diff mbox series

[ovs-dev,v13] OVN - Add Support for Remote Port Mirroring

Message ID 20221104190908.346220-1-abhiramrn@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,v13] OVN - Add Support for Remote Port Mirroring | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Abhiram RN Nov. 4, 2022, 7:09 p.m. UTC
Mirror creation just creates the mirror. The lsp-attach-mirror
triggers the sequence to create Mirror in OVS DB on compute node.
OVS already supports Port Mirroring.

Note: This is targeted to mirror to destinations anywhere outside the
cluster where the analyser resides and it need not be an OVN node.

Example commands are as below:

Mirror creation
ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2

Attach a logical port to the mirror.
ovn-nbctl lsp-attach-mirror sw0-port1 mirror1

Detach a source from Mirror
ovn-nbctl lsp-detach-mirror sw0-port1 mirror1

Mirror deletion
ovn-nbctl mirror-del mirror1

Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
---
v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
             test to make it pass consistently.
V11 --> V12: Minor fix in ovn.at to solve intermittent failures

V10 --> V11: Addressed review comments from V10 by Ihar
           i) Expanded bulk updates test cases in ovn.at
              Overall below cases are covered
              a) Attaches multiple mirrors (new)
              b) Equal detaches and attaches (same as V10)
              c) Detaches more than attaches (new)
              d) Attaches more than detaches (new)
              e) Detaches all (new)
          ii) Addressed the detach all case in mirror.c
         iii) Minor correction in NEWS
          iv) Added invalid mirror attach case in ovn-nbctl.at

Files modified (V10 --> V11):
Code --> mirror.c
Test --> ovn.at, ovn-nbctl.at
Misc --> NEWS

Ihar,
    Regarding mirror_delete function param delete_all it is wrt the
port binding and if a port binding is removed we delete all its
attachment. Already that use case is covered in ovn.at.
Having said that the detaches all had issue in mirror_delete which
I have addressed. With all the above cases added now in bulk updates
hope it should give good assurance.

 NEWS                        |   1 +
 controller/automake.mk      |   4 +-
 controller/mirror.c         | 538 +++++++++++++++++++++++++
 controller/mirror.h         |  53 +++
 controller/ovn-controller.c | 266 ++++++++++--
 northd/en-northd.c          |   4 +
 northd/inc-proc-northd.c    |   4 +
 northd/northd.c             | 172 ++++++++
 northd/northd.h             |   2 +
 ovn-nb.ovsschema            |  31 +-
 ovn-nb.xml                  |  63 +++
 ovn-sb.ovsschema            |  26 +-
 ovn-sb.xml                  |  50 +++
 tests/ovn-nbctl.at          | 120 ++++++
 tests/ovn-northd.at         | 102 +++++
 tests/ovn.at                | 778 ++++++++++++++++++++++++++++++++++++
 utilities/ovn-nbctl.c       | 357 +++++++++++++++++
 utilities/ovn-sbctl.c       |   4 +
 18 files changed, 2547 insertions(+), 28 deletions(-)
 create mode 100644 controller/mirror.c
 create mode 100644 controller/mirror.h

Comments

0-day Robot Nov. 4, 2022, 7:19 p.m. UTC | #1
Bleep bloop.  Greetings Abhiram R N, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line lacks whitespace around operator
#2593 FILE: utilities/ovn-nbctl.c:275:
  mirror-add NAME TYPE INDEX FILTER IP\n\

WARNING: Line lacks whitespace around operator
#2602 FILE: utilities/ovn-nbctl.c:284:
  mirror-del [NAME]         remove mirrors\n\

WARNING: Line lacks whitespace around operator
#2603 FILE: utilities/ovn-nbctl.c:285:
  mirror-list               print mirrors\n\

WARNING: Line lacks whitespace around operator
#2612 FILE: utilities/ovn-nbctl.c:327:
  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\

WARNING: Line lacks whitespace around operator
#2613 FILE: utilities/ovn-nbctl.c:328:
  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\

Lines checked: 3012, Warnings: 5, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Ihar Hrachyshka Nov. 15, 2022, 10:42 p.m. UTC | #2
I think there's a problem with the bulk tests added in this patch. I 
will cover this issue in this email, and I'll send my code review 
tomorrow as promised, since it's getting late here.


When running the whole suite locally, I get the following failures:


401: Mirror test bulk swap attachments -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425)
402: Mirror test bulk swap attachments -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425)
403: Mirror test bulk attach multiple -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537)
410: Mirror test bulk more detach and less attach -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=no ok
412: Mirror test bulk attach more than detach -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=no ok
416: Mirror test bulk detach multiple -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=no ok
408: Mirror test bulk more detach and less attach -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650)
417: Mirror test bulk detach multiple -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=yes ok
409: Mirror test bulk more detach and less attach -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650)
411: Mirror test bulk attach more than detach -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766)
418: Mirror test bulk detach multiple -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=no ok
413: Mirror test bulk attach more than detach -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766)
415: Mirror test bulk detach multiple -- ovn-northd -- 
parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879)
414: Mirror test bulk attach more than detach -- ovn-northd -- 
parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766)


Note that some of the test case variants passed, and I don't think 
there's a clear pattern as to which of variants in the test matrix do fail.


The error that triggers the failure is during ovs-vswitchd cleanup:


./ovn.at:16425: ovs-appctl --timeout=10 -t ovs-vswitchd exit --cleanup
--- /dev/null   2022-11-04 04:09:25.869645998 +0000
+++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr 
2022-11-15 16:31:15.557479369 +0000
@@ -0,0 +1,2 @@
+2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with signal 14 
(Alarm clock)
+/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source: line 
282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t 
ovs-vswitchd exit --cleanup
./ovn.at:16425: exit code was 142, expected 0


The very last message in ovs-vswitchd log on hv1 is exactly 10 seconds 
before the alarm clock error:


2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max 
translation depth 64 on bridge br-int while processing 
arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00


I don't see coredumps generated for any of test processes, so it's 
probably not the case of ovs-vswitchd crashing on exit request.


I tried to adjust your test cases to a minimal reproducer and I found 
that if a test case creates two mirrors, both of to-lport type, then 
ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl 
requests, nor it handles new ports. But if I merely change the type of 
one of mirrors in the test to from-lport, the test passes.


On the other hand, a consistent way to trigger the failure is adding a 
'sleep 3' at the end of a test case just before cleanup, apparently to 
allow vswitchd to catch on the mirror updates and lock somewhere in the 
code. I see vswitchd spinning at ~100% cpu in 'top' output when it gets 
into this state. It's clearly doing SOMETHING, not just sleeping. :)


I suspect there's some bug inside vswitchd that makes it lock / spin for 
a particular setup of mirrors. Whatever OVN sets up in vswitchd 
database, the latter should not freeze. It would be helpful to provide a 
short ovs-only reproducer for the situation that would not involve OVN 
so that our OVS friends can take a look.


For the record, the mirrors in ovsdb are:


_uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
external_ids        : {}
name                : mirror0
output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
output_vlan         : []
select_all          : false
select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab, 
7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
select_src_port     : []
select_vlan         : []
snaplen             : []
statistics          : {}

_uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
external_ids        : {}
name                : mirror1
output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
output_vlan         : []
select_all          : false
select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6, 
4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
select_src_port     : []
select_vlan         : []
snaplen             : []
statistics          : {}


Bridge output here:


8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
     Bridge br-int
         fail_mode: secure
         datapath_type: system
         Port vif4
             Interface vif4
         Port vif2
             Interface vif2
         Port br-int
             Interface br-int
                 type: internal
         Port vif1
             Interface vif1
         Port ovn-mirror0
             Interface ovn-mirror0
                 type: gre
                 options: {key="0", remote_ip="192.168.1.12"}
         Port vif3
             Interface vif3
         Port ovn-mirror1
             Interface ovn-mirror1
                 type: gre
                 options: {key="1", remote_ip="192.168.1.12"}
         Port patch-br-int-to-ln-public
             Interface patch-br-int-to-ln-public
                 type: patch
                 options: {peer=patch-ln-public-to-br-int}
     Bridge br-phys
         Port br-phys
             Interface br-phys
                 type: internal
                 options: 
{rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap", 
tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
         Port br-phys_n1
             Interface br-phys_n1
                 options: 
{rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap", 
stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock", 
tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
         Port patch-ln-public-to-br-int
             Interface patch-ln-public-to-br-int
                 type: patch
                 options: {peer=patch-br-int-to-ln-public}

On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>> Mirror creation just creates the mirror. The lsp-attach-mirror
>> triggers the sequence to create Mirror in OVS DB on compute node.
>> OVS already supports Port Mirroring.
>>
>> Note: This is targeted to mirror to destinations anywhere outside the
>> cluster where the analyser resides and it need not be an OVN node.
>>
>> Example commands are as below:
>>
>> Mirror creation
>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>>
>> Attach a logical port to the mirror.
>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>>
>> Detach a source from Mirror
>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>>
>> Mirror deletion
>> ovn-nbctl mirror-del mirror1
>>
>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
>> ---
>> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
>>               test to make it pass consistently.
>> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
>>
>> V10 --> V11: Addressed review comments from V10 by Ihar
>>             i) Expanded bulk updates test cases in ovn.at
>>                Overall below cases are covered
>>                a) Attaches multiple mirrors (new)
>>                b) Equal detaches and attaches (same as V10)
>>                c) Detaches more than attaches (new)
>>                d) Attaches more than detaches (new)
>>                e) Detaches all (new)
>>            ii) Addressed the detach all case in mirror.c
>>           iii) Minor correction in NEWS
>>            iv) Added invalid mirror attach case in ovn-nbctl.at
>>
>> Files modified (V10 --> V11):
>> Code --> mirror.c
>> Test --> ovn.at, ovn-nbctl.at
>> Misc --> NEWS
>>
>> Ihar,
>>      Regarding mirror_delete function param delete_all it is wrt the
>> port binding and if a port binding is removed we delete all its
>> attachment. Already that use case is covered in ovn.at.
>> Having said that the detaches all had issue in mirror_delete which
>> I have addressed. With all the above cases added now in bulk updates
>> hope it should give good assurance.
>>
>>   NEWS                        |   1 +
>>   controller/automake.mk      |   4 +-
>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>>   controller/mirror.h         |  53 +++
>>   controller/ovn-controller.c | 266 ++++++++++--
>>   northd/en-northd.c          |   4 +
>>   northd/inc-proc-northd.c    |   4 +
>>   northd/northd.c             | 172 ++++++++
>>   northd/northd.h             |   2 +
>>   ovn-nb.ovsschema            |  31 +-
>>   ovn-nb.xml                  |  63 +++
>>   ovn-sb.ovsschema            |  26 +-
>>   ovn-sb.xml                  |  50 +++
>>   tests/ovn-nbctl.at          | 120 ++++++
>>   tests/ovn-northd.at         | 102 +++++
>>   tests/ovn.at                | 778 ++++++++++++++++++++++++++++++++++++
>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>>   utilities/ovn-sbctl.c       |   4 +
>>   18 files changed, 2547 insertions(+), 28 deletions(-)
>>   create mode 100644 controller/mirror.c
>>   create mode 100644 controller/mirror.h
>>
>> diff --git a/NEWS b/NEWS
>> index 224a7b83e..84b22abdb 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>>       any of LR's LRP IP, there is no need to create SNAT entry.  Now such
>>       traffic destined to LRP IP is not dropped.
>>     - Bump python version required for building OVN to 3.6.
>> +  - Added Support for Remote Port Mirroring.
>>
>>   OVN v22.06.0 - 03 Jun 2022
>>   --------------------------
>> diff --git a/controller/automake.mk b/controller/automake.mk
>> index c2ab1bbe6..334672b4d 100644
>> --- a/controller/automake.mk
>> +++ b/controller/automake.mk
>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>>          controller/ovsport.h \
>>          controller/ovsport.c \
>>          controller/vif-plug.h \
>> -       controller/vif-plug.c
>> +       controller/vif-plug.c \
>> +       controller/mirror.h \
>> +       controller/mirror.c
>>
>>   controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
>>   man_MANS += controller/ovn-controller.8
>> diff --git a/controller/mirror.c b/controller/mirror.c
>> new file mode 100644
>> index 000000000..11f2b63a6
>> --- /dev/null
>> +++ b/controller/mirror.c
>> @@ -0,0 +1,538 @@
>> +/* Copyright (c) 2022 Red Hat, Inc.
>> + *
>> + * Licensed under the Apache License, Version 2.0 (the "License");
>> + * you may not use this file except in compliance with the License.
>> + * You may obtain a copy of the License at:
>> + *
>> + *     http://www.apache.org/licenses/LICENSE-2.0
>> + *
>> + * Unless required by applicable law or agreed to in writing, software
>> + * distributed under the License is distributed on an "AS IS" BASIS,
>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>> + * See the License for the specific language governing permissions and
>> + * limitations under the License.
>> + */
>> +
>> +#include <config.h>
>> +#include <unistd.h>
>> +
>> +/* library headers */
>> +#include "lib/sset.h"
>> +#include "lib/util.h"
>> +
>> +/* OVS includes. */
>> +#include "lib/vswitch-idl.h"
>> +#include "openvswitch/vlog.h"
>> +
>> +/* OVN includes. */
>> +#include "binding.h"
>> +#include "lib/ovn-sb-idl.h"
>> +#include "mirror.h"
>> +
>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
>> +
>> +/* Static function declarations */
>> +
>> +static const struct ovsrec_port *
>> +get_port_for_iface(const struct ovsrec_interface *iface,
>> +                  const struct ovsrec_bridge *br_int)
>> +{
>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
>> +        const struct ovsrec_port *p = br_int->ports[i];
>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
>> +                return p;
>> +            }
>> +        }
>> +    }
>> +    return NULL;
>> +}
>> +
>> +static bool
>> +mirror_create(const struct sbrec_port_binding *pb,
>> +              struct port_mirror_ctx *pm_ctx)
>> +{
>> +    const struct ovsrec_mirror *mirror = NULL;
>> +
>> +    if (pb->n_up && !pb->up[0]) {
>> +        return true;
>> +    }
>> +
>> +    if (pb->chassis != pm_ctx->chassis_rec) {
>> +        return true;
>> +    }
>> +
>> +    if (!pm_ctx->ovs_idl_txn) {
>> +        return false;
>> +    }
>> +
>> +
>> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>> +    /* Loop through the mirror rules */
>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>> +        /* check if the mirror already exists in OVS DB */
>> +        bool create_mirror = true;
>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>> +                /* Mirror with same name already exists
>> +                 * No need to create mirror
>> +                 */
>> +                create_mirror = false;
>> +                break;
>> +            }
>> +        }
>> +
>> +        if (create_mirror) {
>> +
>> +            struct smap options = SMAP_INITIALIZER(&options);
>> +            char *port_name, *key;
>> +
>> +            key = xasprintf("%ld",(long int) pb->mirror_rules[i]->index);
>> +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
>> +            smap_add(&options, "key", key);
>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>> +                /* Set the ERSPAN index */
>> +                smap_add(&options, "erspan_idx", key);
>> +                smap_add(&options, "erspan_ver","1");
>> +
>> +            }
>> +            struct ovsrec_interface *iface =
>> +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>> +            port_name = xasprintf("ovn-%s",
>> +                                   pb->mirror_rules[i]->name);
>> +
>> +            ovsrec_interface_set_name(iface, port_name);
>> +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
>> +            ovsrec_interface_set_options(iface, &options);
>> +
>> +            struct ovsrec_port *port =
>> +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>> +            ovsrec_port_set_name(port, port_name);
>> +            ovsrec_port_set_interfaces(port, &iface, 1);
>> +
>> +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>> +
>> +            smap_destroy(&options);
>> +            free(port_name);
>> +            free(key);
>> +
>> +            VLOG_INFO("Creating Mirror in OVS DB");
>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>> +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
>> +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>> +                                                             mirror);
>> +        }
>> +
>> +        struct local_binding *lbinding = local_binding_find(
>> +                               pm_ctx->local_bindings, pb->logical_port);
>> +        const struct ovsrec_port *p =
>> +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
>> +        if (p) {
>> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> +            } else {
>> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> +            }
>> +        }
>> +    }
>> +    return true;
>> +}
>> +
>> +static void
>> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
>> +                              struct ovsrec_mirror *ovs_mirror)
>> +{
>> +    char *filter;
>> +    if ((ovs_mirror->n_select_dst_port)
>> +            && (ovs_mirror->n_select_src_port)) {
>> +        filter = "both";
>> +    } else if (ovs_mirror->n_select_dst_port) {
>> +        filter = "to-lport";
>> +    } else {
>> +        filter = "from-lport";
>> +    }
>> +
>> +    if (strcmp(sb_mirror->filter, filter)) {
>> +        if (!strcmp(sb_mirror->filter,"from-lport")
>> +                              && !strcmp(filter,"both")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> +                                             ovs_mirror->select_dst_port[i]);
>> +            }
>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> +                              && !strcmp(filter,"both")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> +                                             ovs_mirror->select_src_port[i]);
>> +            }
>> +        } else if (!strcmp(sb_mirror->filter,"both")
>> +                              && !strcmp(filter,"from-lport")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> +                                             ovs_mirror->select_src_port[i]);
>> +            }
>> +        } else if (!strcmp(sb_mirror->filter,"both")
>> +                              && !strcmp(filter,"to-lport")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> +                                             ovs_mirror->select_dst_port[i]);
>> +            }
>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> +                              && !strcmp(filter,"from-lport")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> +                                             ovs_mirror->select_src_port[i]);
>> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> +                                             ovs_mirror->select_src_port[i]);
>> +            }
>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>> +                              && !strcmp(filter,"to-lport")) {
>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> +                                             ovs_mirror->select_dst_port[i]);
>> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> +                                             ovs_mirror->select_dst_port[i]);
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +static void
>> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
>> +                                   struct ovsrec_mirror *ovs_mirror)
>> +{
>> +    struct smap options = SMAP_INITIALIZER(&options);
>> +    char *key, *type;
>> +    struct ovsrec_interface *iface =
>> +                          ovs_mirror->output_port->interfaces[0];
>> +    struct smap *opts = &iface->options;
>> +
>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>> +    if (erspan_ver) {
>> +        type = "erspan";
>> +    } else {
>> +        type = "gre";
>> +    }
>> +    if (strcmp(type, sb_mirror->type)) {
>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
>> +    }
>> +
>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
>> +    smap_add(&options, "key", key);
>> +
>> +    if (!strcmp(sb_mirror->type, "erspan")) {
>> +        /* Set the ERSPAN index */
>> +        smap_add(&options, "erspan_idx", key);
>> +        smap_add(&options, "erspan_ver","1");
>> +    }
>> +
>> +    ovsrec_interface_set_options(iface, &options);
>> +    smap_destroy(&options);
>> +    free(key);
>> +
>> +}
>> +
>> +static void
>> +mirror_update(const struct sbrec_mirror *sb_mirror,
>> +              struct ovsrec_mirror *ovs_mirror)
>> +{
>> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
>> +
>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>> +}
>> +
>> +static bool
>> +mirror_delete(const struct sbrec_port_binding *pb,
>> +              struct port_mirror_ctx *pm_ctx,
>> +              struct shash *pb_mirror_map,
>> +              bool delete_all)
>> +{
>> +
>> +    if (!pm_ctx->ovs_idl_txn) {
>> +        return false;
>> +    }
>> +
>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>> +
>> +    if (!delete_all) {
>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>> +        }
>> +    }
>> +
>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> +
>> +            struct ovsrec_mirror *ovs_mirror = NULL;
>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>> +                                            pb->mirror_rules[i]->name);
>> +            if (ovs_mirror) {
>> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> +                                               ovs_mirror->output_port);
>> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> +                                                            ovs_mirror);
>> +                ovsrec_port_delete(ovs_mirror->output_port);
>> +                ovsrec_mirror_delete(ovs_mirror);
>> +            }
>> +        }
>> +    }
>> +
>> +    struct shash_node *mirror_node;
>> +    const struct sbrec_port_binding *sb_pb;
>> +    int attach_cnt = 0;
>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>> +            /* Find if the mirror has other sources */
>> +            attach_cnt = 0;
>> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>> +                                       pm_ctx->port_binding_table) {
>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
>> +                                                ovs_mirror->name)) {
>> +                        attach_cnt++;
>> +                    }
>> +                }
>> +            }
>> +            if (attach_cnt) {
>> +                /* More than 1 source then just
>> +                 * update the mirror table
>> +                 */
>> +                bool done = false;
>> +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
>> +                                                   && (done == false)); i++) {
>> +                    const struct ovsrec_port *port_rec =
>> +                                               ovs_mirror->select_dst_port[i];
>> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> +                        const struct ovsrec_interface *iface_rec;
>> +
>> +                        iface_rec = port_rec->interfaces[j];
>> +                        const char *iface_id =
>> +                                            smap_get(&iface_rec->external_ids,
>> +                                                                  "iface-id");
>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>> +                            ovsrec_mirror_update_select_dst_port_delvalue(
>> +                                                        ovs_mirror, port_rec);
>> +                            done = true;
>> +                            break;
>> +                        }
>> +                    }
>> +                }
>> +                done = false;
>> +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
>> +                                                   && (done == false)); i++) {
>> +                    const struct ovsrec_port *port_rec =
>> +                                                ovs_mirror->select_src_port[i];
>> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> +                        const struct ovsrec_interface *iface_rec;
>> +
>> +                        iface_rec = port_rec->interfaces[j];
>> +                        const char *iface_id =
>> +                                            smap_get(&iface_rec->external_ids,
>> +                                                                  "iface-id");
>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>> +                            ovsrec_mirror_update_select_src_port_delvalue(
>> +                                                        ovs_mirror, port_rec);
>> +                            done = true;
>> +                            break;
>> +                        }
>> +                    }
>> +                }
>> +            } else {
>> +                /*
>> +                 * If only 1 source delete the output port
>> +                 * and then delete the mirror completely
>> +                 */
>> +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
>> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> +                                                    ovs_mirror->output_port);
>> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> +                                                            ovs_mirror);
>> +                ovsrec_port_delete(ovs_mirror->output_port);
>> +                ovsrec_mirror_delete(ovs_mirror);
>> +            }
>> +        }
>> +    }
>> +
>> +    const char *used_node, *used_next;
>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>> +    }
>> +    sset_destroy(&pb_mirrors);
>> +
>> +    return true;
>> +}
>> +
>> +static void
>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>> +                            struct port_mirror_ctx *pm_ctx,
>> +                            struct shash *pb_mirror_map)
>> +{
>> +    const struct ovsrec_mirror *mirror = NULL;
>> +
>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>> +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> +                const struct ovsrec_interface *iface_rec;
>> +                iface_rec = port_rec->interfaces[j];
>> +                const char *logical_port =
>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>> +                if (!strcmp(logical_port, pb->logical_port)) {
>> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>> +                }
>> +            }
>> +        }
>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>> +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> +                const struct ovsrec_interface *iface_rec;
>> +                iface_rec = port_rec->interfaces[j];
>> +                const char *logical_port =
>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>> +                if (!strcmp(logical_port, pb->logical_port)) {
>> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>> +                }
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +void
>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> +{
>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>> +
>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
>> +}
>> +
>> +
>> +void
>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
>> +{
>> +    shash_init(ovs_mirrors);
>> +}
>> +
>> +void
>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>> +{
>> +    const struct sbrec_port_binding *pb;
>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>> +                                       pm_ctx->port_binding_table) {
>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>> +    }
>> +}
>> +
>> +bool
>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
>> +                     struct port_mirror_ctx *pm_ctx)
>> +{
>> +    bool ret = true;
>> +    struct local_binding *lbinding = local_binding_find(
>> +                               pm_ctx->local_bindings, pb->logical_port);
>> +
>> +    if (strcmp(pb->type, "") && (!lbinding)) {
>> +        return ret;
>> +    }
>> +
>> +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
>> +
>> +    /* Need to find if mirror needs update */
>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>> +    if (!removed) {
>> +        if ((pb->n_mirror_rules == 0)
>> +              && (shash_is_empty(&port_ovs_mirrors))) {
>> +            /* No mirror update */
>> +        } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) {
>> +            /* Though number of mirror rules are same,
>> +             * need to verify the contents
>> +             */
>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
>> +                if (!shash_find(&port_ovs_mirrors,
>> +                               pb->mirror_rules[i]->name)) {
>> +                    /* Mis match in OVN SB DB and OVS DB
>> +                     * Delete and Create mirror(s) with proper sources
>> +                     */
>> +                    ret = mirror_delete(pb, pm_ctx,
>> +                                        &port_ovs_mirrors, false);
>> +                    if (ret) {
>> +                        ret = mirror_create(pb, pm_ctx);
>> +                    }
>> +                    break;
>> +                }
>> +            }
>> +        } else {
>> +            /* Update Mirror */
>> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
>> +                /* create mirror,
>> +                 * if mirror already exists only update selection
>> +                 */
>> +                ret = mirror_create(pb, pm_ctx);
>> +            } else {
>> +                /* delete mirror,
>> +                 * if mirror has other sources only update selection
>> +                 */
>> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
>> +            }
>> +        }
>> +    } else {
>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>> +    }
>> +
>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> +                                              &port_ovs_mirrors) {
>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>> +    }
>> +    shash_destroy(&port_ovs_mirrors);
>> +
>> +    return ret;
>> +}
>> +
>> +bool
>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>> +{
>> +    const struct sbrec_mirror *mirror = NULL;
>> +    struct ovsrec_mirror *ovs_mirror = NULL;
>> +
>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
>> +    /* For each tracked mirror entry check if OVS entry is there*/
>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
>> +        if (ovs_mirror) {
>> +            if (sbrec_mirror_is_deleted(mirror)) {
>> +                /* Need to delete the mirror in OVS */
>> +                VLOG_INFO("Delete mirror and remove port");
>> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> +                                                    ovs_mirror->output_port);
>> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> +                                                      ovs_mirror);
>> +                ovsrec_port_delete(ovs_mirror->output_port);
>> +                ovsrec_mirror_delete(ovs_mirror);
>> +            } else {
>> +                mirror_update(mirror, ovs_mirror);
>> +            }
>> +        }
>> +    }
>> +
>> +    return true;
>> +}
>> +
>> +void
>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>> +{
>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> +                                              ovs_mirrors) {
>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
>> +    }
>> +    shash_destroy(ovs_mirrors);
>> +}
>> diff --git a/controller/mirror.h b/controller/mirror.h
>> new file mode 100644
>> index 000000000..85b964f55
>> --- /dev/null
>> +++ b/controller/mirror.h
>> @@ -0,0 +1,53 @@
>> +/* Copyright (c) 2022 Red Hat, Inc.
>> + *
>> + * Licensed under the Apache License, Version 2.0 (the "License");
>> + * you may not use this file except in compliance with the License.
>> + * You may obtain a copy of the License at:
>> + *
>> + *     http://www.apache.org/licenses/LICENSE-2.0
>> + *
>> + * Unless required by applicable law or agreed to in writing, software
>> + * distributed under the License is distributed on an "AS IS" BASIS,
>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>> + * See the License for the specific language governing permissions and
>> + * limitations under the License.
>> + */
>> +
>> +#ifndef OVN_MIRROR_H
>> +#define OVN_MIRROR_H 1
>> +
>> +struct ovsdb_idl_txn;
>> +struct ovsrec_port_table;
>> +struct ovsrec_bridge;
>> +struct ovsrec_bridge_table;
>> +struct ovsrec_open_vswitch_table;
>> +struct sbrec_chassis;
>> +struct ovsrec_interface_table;
>> +struct ovsrec_mirror_table;
>> +struct sbrec_mirror_table;
>> +struct sbrec_port_binding_table;
>> +
>> +struct port_mirror_ctx {
>> +    struct shash *ovs_mirrors;
>> +    struct ovsdb_idl_txn *ovs_idl_txn;
>> +    const struct ovsrec_port_table *port_table;
>> +    const struct ovsrec_bridge *br_int;
>> +    const struct sbrec_chassis *chassis_rec;
>> +    const struct ovsrec_bridge_table *bridge_table;
>> +    const struct ovsrec_open_vswitch_table *ovs_table;
>> +    const struct ovsrec_interface_table *iface_table;
>> +    const struct ovsrec_mirror_table *mirror_table;
>> +    const struct sbrec_mirror_table *sb_mirror_table;
>> +    const struct sbrec_port_binding_table *port_binding_table;
>> +    struct shash *local_bindings;
>> +};
>> +
>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
>> +void ovn_port_mirror_init(struct shash *);
>> +void ovn_port_mirror_destroy(struct shash *);
>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
>> +                                  bool removed,
>> +                                  struct port_mirror_ctx *pm_ctx);
>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
>> +#endif
>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>> index 8895c7a2b..15ab17c4a 100644
>> --- a/controller/ovn-controller.c
>> +++ b/controller/ovn-controller.c
>> @@ -78,6 +78,7 @@
>>   #include "lib/inc-proc-eng.h"
>>   #include "lib/ovn-l7.h"
>>   #include "hmapx.h"
>> +#include "mirror.h"
>>
>>   VLOG_DEFINE_THIS_MODULE(main);
>>
>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
>> +    mirror_register_ovs_idl(ovs_idl);
>>   }
>>
>>   #define SB_NODES \
>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>       SB_NODE(load_balancer, "load_balancer") \
>>       SB_NODE(fdb, "fdb") \
>>       SB_NODE(meter, "meter") \
>> +    SB_NODE(mirror, "mirror") \
>>       SB_NODE(static_mac_binding, "static_mac_binding")
>>
>>   enum sb_engine_node {
>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>>       OVS_NODE(bridge, "bridge") \
>>       OVS_NODE(port, "port") \
>>       OVS_NODE(interface, "interface") \
>> -    OVS_NODE(qos, "qos")
>> +    OVS_NODE(qos, "qos") \
>> +    OVS_NODE(mirror, "mirror")
>>
>>   enum ovs_engine_node {
>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>>       free(lbs);
>>   }
>>
>> +/* Mirror Engine */
>> +struct ed_type_port_mirror {
>> +    struct shash ovs_mirrors;
>> +};
>> +
>> +static void *
>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>> +                    struct engine_arg *arg OVS_UNUSED)
>> +{
>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
>> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>> +    return port_mirror;
>> +}
>> +
>> +static void
>> +en_port_mirror_cleanup(void *data)
>> +{
>> +    struct ed_type_port_mirror *port_mirror = data;
>> +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>> +}
>> +
>> +static void
>> +init_port_mirror_ctx(struct engine_node *node,
>> +                 struct ed_type_runtime_data *rt_data,
>> +                 struct ed_type_port_mirror *port_mirror_data,
>> +                 struct port_mirror_ctx *pm_ctx)
>> +{
>> +    struct ovsrec_open_vswitch_table *ovs_table =
>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>> +            engine_get_input("OVS_open_vswitch", node));
>> +    struct ovsrec_bridge_table *bridge_table =
>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>> +            engine_get_input("OVS_bridge", node));
>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
>> +
>> +    ovs_assert(br_int && chassis_id);
>> +    const struct sbrec_chassis *chassis = NULL;
>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>> +        engine_ovsdb_node_get_index(
>> +                engine_get_input("SB_chassis", node),
>> +                "name");
>> +
>> +    if (chassis_id) {
>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
>> +    }
>> +    ovs_assert(chassis);
>> +
>> +    struct ovsrec_port_table *port_table =
>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>> +            engine_get_input("OVS_port", node));
>> +
>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
>> +        engine_get_input_data("ovs_interface_shadow", node);
>> +
>> +    struct ovsrec_mirror_table *mirror_table =
>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>> +            engine_get_input("OVS_mirror", node));
>> +
>> +    struct sbrec_port_binding_table *pb_table =
>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>> +            engine_get_input("SB_port_binding", node));
>> +
>> +    struct sbrec_mirror_table *sb_mirror_table =
>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>> +            engine_get_input("SB_mirror", node));
>> +
>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> +                           &port_mirror_data->ovs_mirrors) {
>> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
>> +    }
>> +
>> +    const struct ovsrec_mirror *ovsmirror = NULL;
>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
>> +    }
>> +
>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>> +    pm_ctx->port_table = port_table;
>> +    pm_ctx->iface_table = iface_shadow->iface_table;
>> +    pm_ctx->mirror_table = mirror_table;
>> +    pm_ctx->port_binding_table = pb_table;
>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
>> +    pm_ctx->br_int = br_int;
>> +    pm_ctx->chassis_rec = chassis;
>> +    pm_ctx->bridge_table = bridge_table;
>> +    pm_ctx->ovs_table = ovs_table;
>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>> +}
>> +
>> +static void
>> +en_port_mirror_run(struct engine_node *node, void *data)
>> +{
>> +    struct port_mirror_ctx pm_ctx;
>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> +    struct ed_type_runtime_data *rt_data =
>> +        engine_get_input_data("runtime_data", node);
>> +
>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> +
>> +    ovn_port_mirror_run(&pm_ctx);
>> +    engine_set_node_state(node, EN_UPDATED);
>> +}
>> +
>> +static bool
>> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
>> +{
>> +    struct ed_type_runtime_data *rt_data =
>> +        engine_get_input_data("runtime_data", node);
>> +
>> +    /* There is no tracked data. Fall back to full recompute of port_mirror */
>> +    if (!rt_data->tracked) {
>> +        return false;
>> +    }
>> +
>> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
>> +    if (hmap_is_empty(tracked_dp_bindings)) {
>> +        return true;
>> +    }
>> +
>> +    struct port_mirror_ctx pm_ctx;
>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> +
>> +    struct tracked_datapath *tdp;
>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>> +            /* Fall back to full recompute when a local datapath
>> +             * is added or deleted. */
>> +            return false;
>> +        }
>> +
>> +        struct shash_node *shash_node;
>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>> +            struct tracked_lport *lport = shash_node->data;
>> +            bool removed =
>> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
>> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
>> +                return false;
>> +            }
>> +        }
>> +    }
>> +
>> +    engine_set_node_state(node, EN_UPDATED);
>> +    return true;
>> +}
>> +
>> +static bool
>> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
>> +{
>> +    struct port_mirror_ctx pm_ctx;
>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> +    struct ed_type_runtime_data *rt_data =
>> +        engine_get_input_data("runtime_data", node);
>> +
>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> +
>> +    /* handle port binding updates (i.,e when the mirror column
>> +     * of port_binding is updated)
>> +     */
>> +    const struct sbrec_port_binding *pb;
>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> +                                               pm_ctx.port_binding_table) {
>> +        bool removed = sbrec_port_binding_is_deleted(pb);
>> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
>> +            return false;
>> +        }
>> +    }
>> +
>> +    engine_set_node_state(node, EN_UPDATED);
>> +    return true;
>> +
>> +}
>> +
>> +static bool
>> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
>> +{
>> +    struct port_mirror_ctx pm_ctx;
>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> +    struct ed_type_runtime_data *rt_data =
>> +        engine_get_input_data("runtime_data", node);
>> +
>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> +
>> +    /* handle sb mirror updates
>> +     */
>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>> +        return false;
>> +    }
>> +
>> +    engine_set_node_state(node, EN_UPDATED);
>> +    return true;
>> +
>> +}
>> +
>>   /* Engine node which is used to handle the Non VIF data like
>>    *   - OVS patch ports
>>    *   - Tunnel ports and the related chassis information.
>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>>       ENGINE_NODE(northd_options, "northd_options");
>>       ENGINE_NODE(dhcp_options, "dhcp_options");
>> +    ENGINE_NODE(port_mirror, "port_mirror");
>>
>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>>       SB_NODES
>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>>       engine_add_input(&en_flow_output, &en_pflow_output,
>>                        flow_output_pflow_output_handler);
>>
>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>> +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>> +                     engine_noop_handler);
>> +    engine_add_input(&en_flow_output, &en_port_mirror,
>> +                     engine_noop_handler);
>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
>> +                     port_mirror_runtime_data_handler);
>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>> +                     port_mirror_sb_mirror_handler);
>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>> +                     port_mirror_port_binding_handler);
>> +
>>       struct engine_arg engine_arg = {
>>           .sb_idl = ovnsb_idl_loop.idl,
>>           .ovs_idl = ovs_idl_loop.idl,
>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>>
>>                       stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>>                                       time_msec());
>> -                    if (ovnsb_idl_txn) {
>> -                        if (ofctrl_has_backlog()) {
>> -                            /* When there are in-flight messages pending to
>> -                             * ovs-vswitchd, we should hold on recomputing so
>> -                             * that the previous flow installations won't be
>> -                             * delayed.  However, we still want to try if
>> -                             * recompute is not needed and we can quickly
>> -                             * incrementally process the new changes, to avoid
>> -                             * unnecessarily forced recomputes later on.  This
>> -                             * is because the OVSDB change tracker cannot
>> -                             * preserve tracked changes across iterations.  If
>> -                             * change tracking is improved, we can simply skip
>> -                             * this round of engine_run and continue processing
>> -                             * acculated changes incrementally later when
>> -                             * ofctrl_has_backlog() returns false. */
>> -                            engine_run(false);
>> -                        } else {
>> -                            engine_run(true);
>> -                        }
>> -                    } else {
>> -                        /* Even if there's no SB DB transaction available,
>> +
>> +                    bool allow_engine_recompute = true;
>> +
>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>> +                                                     ofctrl_has_backlog()) {
>> +                        /* When there are in-flight messages pending to
>> +                         * ovs-vswitchd, we should hold on recomputing so
>> +                         * that the previous flow installations won't be
>> +                         * delayed.  However, we still want to try if
>> +                         * recompute is not needed and we can quickly
>> +                         * incrementally process the new changes, to avoid
>> +                         * unnecessarily forced recomputes later on.  This
>> +                         * is because the OVSDB change tracker cannot
>> +                         * preserve tracked changes across iterations.  If
>> +                         * change tracking is improved, we can simply skip
>> +                         * this round of engine_run and continue processing
>> +                         * acculated changes incrementally later when
>> +                         * ofctrl_has_backlog() returns false. */
>> +
>> +                        /* Even if there's no SB/OVS DB transaction available,
>>                            * try to run the engine so that we can handle any
>>                            * incremental changes that don't require a recompute.
>>                            * If a recompute is required, the engine will abort,
>>                            * triggerring a full run in the next iteration.
>>                            */
>> -                        engine_run(false);
>> +                        allow_engine_recompute = false;
>>                       }
>> +
>> +                    engine_run(allow_engine_recompute);
>> +
>>                       stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>>                                      time_msec());
>>                       if (engine_has_updated()) {
>> diff --git a/northd/en-northd.c b/northd/en-northd.c
>> index 7fe83db64..608220b1f 100644
>> --- a/northd/en-northd.c
>> +++ b/northd/en-northd.c
>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void *data)
>>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
>>       input_data.nbrec_static_mac_binding_table =
>>           EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>> +    input_data.nbrec_mirror_table =
>> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>>
>>       input_data.sbrec_sb_global_table =
>>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, void *data)
>>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>>       input_data.sbrec_static_mac_binding_table =
>>           EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>> +    input_data.sbrec_mirror_table =
>> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>>
>>       northd_run(&input_data, data,
>>                  eng_ctx->ovnnb_idl_txn,
>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>> index 54e0ad3b0..ac27a730e 100644
>> --- a/northd/inc-proc-northd.c
>> +++ b/northd/inc-proc-northd.c
>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>       NB_NODE(acl, "acl") \
>>       NB_NODE(logical_router, "logical_router") \
>>       NB_NODE(qos, "qos") \
>> +    NB_NODE(mirror, "mirror") \
>>       NB_NODE(meter, "meter") \
>>       NB_NODE(meter_band, "meter_band") \
>>       NB_NODE(logical_router_port, "logical_router_port") \
>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>       SB_NODE(logical_flow, "logical_flow") \
>>       SB_NODE(logical_dp_group, "logical_DP_group") \
>>       SB_NODE(multicast_group, "multicast_group") \
>> +    SB_NODE(mirror, "mirror") \
>>       SB_NODE(meter, "meter") \
>>       SB_NODE(meter_band, "meter_band") \
>>       SB_NODE(datapath_binding, "datapath_binding") \
>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>> diff --git a/northd/northd.c b/northd/northd.c
>> index b7388afc5..52abdda28 100644
>> --- a/northd/northd.c
>> +++ b/northd/northd.c
>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>>       free(requested_chassis_sb);
>>   }
>>
>> +static void
>> +do_sb_mirror_addition(struct northd_input *input_data,
>> +                      const struct ovn_port *op)
>> +{
>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> +        const struct sbrec_mirror *sb_mirror;
>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>> +                                     input_data->sbrec_mirror_table) {
>> +            if (!strcmp(sb_mirror->name,
>> +                        op->nbsp->mirror_rules[i]->name)) {
>> +                /* Add the value to SB */
>> +                sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>> +                                                                sb_mirror);
>> +            }
>> +        }
>> +    }
>> +}
>> +
>> +static void
>> +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
>> +                                       const struct ovn_port *op)
>> +{
>> +    size_t i = 0;
>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>> +        /* Needs deletion in SB */
>> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> +            shash_add(&nb_mirror_rules,
>> +                                 op->nbsp->mirror_rules[i]->name,
>> +                                 op->nbsp->mirror_rules[i]);
>> +        }
>> +
>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>> +            if (!shash_find(&nb_mirror_rules,
>> +                           op->sb->mirror_rules[i]->name)) {
>> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>> +                                                 op->sb->mirror_rules[i]);
>> +            }
>> +        }
>> +
>> +        struct shash_node *node, *next;
>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>> +            shash_delete(&nb_mirror_rules, node);
>> +        }
>> +        shash_destroy(&nb_mirror_rules);
>> +
>> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
>> +        /* Needs addition in SB */
>> +        do_sb_mirror_addition(input_data, op);
>> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
>> +        /*
>> +         * Check if its the same mirrors on both SB and NB DBs
>> +         * If not update accordingly.
>> +         */
>> +        bool needs_sb_addition = false;
>> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> +            shash_add(&nb_mirror_rules,
>> +                                 op->nbsp->mirror_rules[i]->name,
>> +                                 op->nbsp->mirror_rules[i]);
>> +        }
>> +
>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>> +            if (!shash_find(&nb_mirror_rules,
>> +                           op->sb->mirror_rules[i]->name)) {
>> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>> +                                                 op->sb->mirror_rules[i]);
>> +                    needs_sb_addition = true;
>> +            }
>> +        }
>> +
>> +        struct shash_node *node, *next;
>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>> +            shash_delete(&nb_mirror_rules, node);
>> +        }
>> +        shash_destroy(&nb_mirror_rules);
>> +
>> +        if (needs_sb_addition) {
>> +            do_sb_mirror_addition(input_data, op);
>> +        }
>> +    }
>> +}
>> +
>>   static void
>>   ovn_port_update_sbrec(struct northd_input *input_data,
>>                         struct ovsdb_idl_txn *ovnsb_txn,
>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>>           }
>>           sbrec_port_binding_set_external_ids(op->sb, &ids);
>>           smap_destroy(&ids);
>> +
>> +        if (!op->nbsp->n_mirror_rules) {
>> +            /* Nothing is set. Clear mirror_rules from pb. */
>> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>> +        } else {
>> +            /* Check if SB DB update needed */
>> +            sbrec_port_binding_update_mirror_rules(input_data, op);
>> +        }
>> +
>>       }
>>       if (op->tunnel_key != op->sb->tunnel_key) {
>>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>>       shash_destroy(&sb_meters);
>>   }
>>
>> +static bool
>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>> +                  const struct sbrec_mirror *sb_mirror)
>> +{
>> +
>> +    if (nb_mirror->index != sb_mirror->index) {
>> +        return true;
>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>> +        return true;
>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>> +        return true;
>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>> +        return true;
>> +    }
>> +
>> +    return false;
>> +}
>> +
>> +static void
>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>> +                             const char *mirror_name,
>> +                             const struct nbrec_mirror *nb_mirror,
>> +                             struct shash *sb_mirrors,
>> +                             struct sset *used_sb_mirrors)
>> +{
>> +    const struct sbrec_mirror *sb_mirror;
>> +    bool new_sb_mirror = false;
>> +
>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>> +    if (!sb_mirror) {
>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>> +        new_sb_mirror = true;
>> +    }
>> +    sset_add(used_sb_mirrors, mirror_name);
>> +
>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
>> +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>> +    }
>> +}
>> +
>> +static void
>> +sync_mirrors(struct northd_input *input_data,
>> +            struct ovsdb_idl_txn *ovnsb_txn)
>> +{
>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
>> +
>> +    const struct sbrec_mirror *sb_mirror;
>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>> +    }
>> +
>> +    const struct nbrec_mirror *nb_mirror;
>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
>> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
>> +                                     &sb_mirrors, &used_sb_mirrors);
>> +    }
>> +
>> +    const char *used_mirror;
>> +    const char *used_mirror_next;
>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
>> +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
>> +    }
>> +    sset_destroy(&used_sb_mirrors);
>> +
>> +    struct shash_node *node, *next;
>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>> +        sbrec_mirror_delete(node->data);
>> +        shash_delete(&sb_mirrors, node);
>> +    }
>> +    shash_destroy(&sb_mirrors);
>> +}
>> +
>>   /*
>>    * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>>    * and Southbound db.
>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>> +    sync_mirrors(input_data, ovnsb_txn);
>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
>>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>> diff --git a/northd/northd.h b/northd/northd.h
>> index da90e2815..17a62ea10 100644
>> --- a/northd/northd.h
>> +++ b/northd/northd.h
>> @@ -36,6 +36,7 @@ struct northd_input {
>>       const struct nbrec_acl_table *nbrec_acl_table;
>>       const struct nbrec_static_mac_binding_table
>>           *nbrec_static_mac_binding_table;
>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>>
>>       /* Southbound table references */
>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
>> @@ -55,6 +56,7 @@ struct northd_input {
>>       const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>>       const struct sbrec_static_mac_binding_table
>>           *sbrec_static_mac_binding_table;
>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>>
>>       /* Indexes */
>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>> index 174364c8b..01de55222 100644
>> --- a/ovn-nb.ovsschema
>> +++ b/ovn-nb.ovsschema
>> @@ -1,7 +1,7 @@
>>   {
>>       "name": "OVN_Northbound",
>> -    "version": "6.3.0",
>> -    "cksum": "4042813038 31869",
>> +    "version": "6.4.0",
>> +    "cksum": "589874483 33352",
>>       "tables": {
>>           "NB_Global": {
>>               "columns": {
>> @@ -132,6 +132,11 @@
>>                                               "refType": "weak"},
>>                                    "min": 0,
>>                                    "max": 1}},
>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> +                                          "refTable": "Mirror",
>> +                                          "refType": "weak"},
>> +                                  "min": 0,
>> +                                  "max": "unlimited"}},
>>                   "ha_chassis_group": {
>>                       "type": {"key": {"type": "uuid",
>>                                        "refTable": "HA_Chassis_Group",
>> @@ -301,6 +306,28 @@
>>                       "type": {"key": "string", "value": "string",
>>                                "min": 0, "max": "unlimited"}}},
>>               "isRoot": false},
>> +        "Mirror": {
>> +            "columns": {
>> +                "name": {"type": "string"},
>> +                "filter": {"type": {"key": {"type": "string",
>> +                                            "enum": ["set", ["from-lport",
>> +                                                             "to-lport",
>> +                                                             "both"]]}}},
>> +                "sink":{"type": "string"},
>> +                "type": {"type": {"key": {"type": "string",
>> +                                            "enum": ["set", ["gre",
>> +                                                             "erspan"]]}}},
>> +                "index": {"type": "integer"},
>> +                "src": {"type": {"key": {"type": "uuid",
>> +                                           "refTable": "Logical_Switch_Port",
>> +                                           "refType": "weak"},
>> +                                   "min": 0,
>> +                                   "max": "unlimited"}},
>> +                "external_ids": {
>> +                    "type": {"key": "string", "value": "string",
>> +                             "min": 0, "max": "unlimited"}}},
>> +            "indexes": [["name"]],
>> +            "isRoot": true},
>>           "Meter": {
>>               "columns": {
>>                   "name": {"type": "string"},
>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>> index f41e9d7c0..d8730c8fc 100644
>> --- a/ovn-nb.xml
>> +++ b/ovn-nb.xml
>> @@ -1554,6 +1554,11 @@
>>         </column>
>>       </group>
>>
>> +    <column name="mirror_rules">
>> +        Mirror rules that apply to logical switch port which is the source.
>> +        Please see the <ref table="Mirror"/> table.
>> +    </column>
>> +
>>       <column name="ha_chassis_group">
>>         References a row in the OVN Northbound database's
>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>> @@ -2491,6 +2496,64 @@
>>       </column>
>>     </table>
>>
>> +  <table name="Mirror" title="Mirror Entry">
>> +    <p>
>> +      Each row in this table represents one Mirror that can be used for
>> +      port mirroring. These Mirrors are referenced by the
>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
>> +      the <ref table="Logical_Switch_Port"/> table.
>> +    </p>
>> +
>> +    <column name="name">
>> +      <p>
>> +        Represents the name of the mirror.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="filter">
>> +      <p>
>> +        The value of this field represents selection criteria of the mirror.
>> +        Supported values for filter to-lport / from-lport / both
>> +        to-lport - to mirror packets coming into logical port
>> +        from-lport - to mirror packets going out of logical port
>> +        both - to mirror packets coming into and going out of logical port.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="sink">
>> +      <p>
>> +        The value of this field represents the destination/sink of the mirror.
>> +        The value it takes is an IP address of the sink port.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="type">
>> +      <p>
>> +        The value of this field represents the type of the tunnel used for
>> +        sending the mirrored packets. Supported Tunnel types gre and erspan
>> +      </p>
>> +    </column>
>> +
>> +    <column name="index">
>> +      <p>
>> +        The value of this field represents the tunnel ID. Depending on the
>> +        tunnel type configured, GRE key value if type GRE and erspan_idx value
>> +        if ERSPAN
>> +      </p>
>> +    </column>
>> +
>> +    <column name="src">
>> +      <p>
>> +        The value of this field represents a list of source ports for the
>> +        mirror. Please see the <ref table="Logical_Switch_Port"/> table.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="external_ids">
>> +      See <em>External IDs</em> at the beginning of this document.
>> +    </column>
>> +  </table>
>> +
>>     <table name="Meter" title="Meter entry">
>>       <p>
>>         Each row in this table represents a meter that can be used for QoS or
>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>> index 576ebbdeb..b83134416 100644
>> --- a/ovn-sb.ovsschema
>> +++ b/ovn-sb.ovsschema
>> @@ -1,7 +1,7 @@
>>   {
>>       "name": "OVN_Southbound",
>> -    "version": "20.25.0",
>> -    "cksum": "53184112 28845",
>> +    "version": "20.26.0",
>> +    "cksum": "2344151793 30004",
>>       "tables": {
>>           "SB_Global": {
>>               "columns": {
>> @@ -142,6 +142,23 @@
>>               "indexes": [["datapath", "tunnel_key"],
>>                           ["datapath", "name"]],
>>               "isRoot": true},
>> +        "Mirror": {
>> +            "columns": {
>> +                "name": {"type": "string"},
>> +                "filter": {"type": {"key": {"type": "string",
>> +                                            "enum": ["set",
>> +                                                     ["from-lport",
>> +                                                      "to-lport","both"]]}}},
>> +                "sink":{"type": "string"},
>> +                "type": {"type": {"key": {"type": "string",
>> +                                            "enum": ["set",
>> +                                                     ["gre", "erspan"]]}}},
>> +                "index": {"type": "integer"},
>> +                "external_ids": {
>> +                    "type": {"key": "string", "value": "string",
>> +                             "min": 0, "max": "unlimited"}}},
>> +            "indexes": [["name"]],
>> +            "isRoot": true},
>>           "Meter": {
>>               "columns": {
>>                   "name": {"type": "string"},
>> @@ -230,6 +247,11 @@
>>                                                         "refTable": "Encap",
>>                                                         "refType": "weak"},
>>                                       "min": 0, "max": "unlimited"}},
>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> +                                          "refTable": "Mirror",
>> +                                          "refType": "weak"},
>> +                                  "min": 0,
>> +                                  "max": "unlimited"}},
>>                   "mac": {"type": {"key": "string",
>>                                    "min": 0,
>>                                    "max": "unlimited"}},
>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>> index 315d60853..05c0db6b4 100644
>> --- a/ovn-sb.xml
>> +++ b/ovn-sb.xml
>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>>       </column>
>>     </table>
>>
>> +  <table name="Mirror" title="Mirror Entry">
>> +    <p>
>> +      Each row in this table represents one Mirror that can be used for
>> +      port mirroring. These Mirrors are referenced by the
>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
>> +      the <ref table="Port_Binding"/> table.
>> +    </p>
>> +
>> +    <column name="name">
>> +      <p>
>> +        Represents the name of the mirror.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="filter">
>> +      <p>
>> +        The value of this field represents selection criteria of the mirror.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="sink">
>> +      <p>
>> +        The value of this field represents the destination/sink of the mirror.
>> +      </p>
>> +    </column>
>> +
>> +    <column name="type">
>> +      <p>
>> +        The value of this field represents the type of the tunnel used for
>> +        sending the mirrored packets
>> +      </p>
>> +    </column>
>> +
>> +    <column name="index">
>> +      <p>
>> +        The value of this field represents the key/idx depending on the
>> +        tunnel type configured
>> +      </p>
>> +    </column>
>> +
>> +    <column name="external_ids">
>> +      See <em>External IDs</em> at the beginning of this document.
>> +    </column>
>> +  </table>
>> +
>>     <table name="Meter" title="Meter entry">
>>       <p>
>>         Each row in this table represents a meter that can be used for QoS or
>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>>         </column>
>>       </group>
>>
>> +    <column name="mirror_rules">
>> +        Mirror rules that apply to the port binding.
>> +        Please see the <ref table="Mirror"/> table.
>> +    </column>
>> +
>>       <group title="Patch Options">
>>         <p>
>>           These options apply to logical ports with <ref column="type"/> of
>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
>> index 4d480e357..d79f9d929 100644
>> --- a/tests/ovn-nbctl.at
>> +++ b/tests/ovn-nbctl.at
>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>>
>>   dnl ---------------------------------------------------------------------
>>
>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
>> +AT_CHECK([ovn-nbctl ls-add sw0])
>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>> +
>> +dnl Add duplicate mirror name
>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>> +
>> +dnl Attach invalid source port to mirror
>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>> +
>> +dnl Attach source port to invalid mirror
>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], [stderr])
>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
>> +
>> +dnl Attach source port to mirror
>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>> +
>> +dnl Attach one more source port to mirror
>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>> +
>> +dnl Verify if multiple ports are attached to the same mirror properly
>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> +mirror1:
>> +  Type     :  gre
>> +  Sink     :  10.10.10.1
>> +  Filter   :  from-lport
>> +  Index/Key:  0
>> +  Sources  :  None attached
>> +mirror2:
>> +  Type     :  erspan
>> +  Sink     :  10.10.10.2
>> +  Filter   :  both
>> +  Index/Key:  1
>> +  Sources  :  None attached
>> +mirror3:
>> +  Type     :  gre
>> +  Sink     :  10.10.10.3
>> +  Filter   :  to-lport
>> +  Index/Key:  2
>> +  Sources  :  sw0-port1  sw0-port3
>> +])
>> +
>> +dnl Detach one source port from mirror
>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>> +
>> +dnl Verify if detach source port from mirror happens properly
>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> +mirror1:
>> +  Type     :  gre
>> +  Sink     :  10.10.10.1
>> +  Filter   :  from-lport
>> +  Index/Key:  0
>> +  Sources  :  None attached
>> +mirror2:
>> +  Type     :  erspan
>> +  Sink     :  10.10.10.2
>> +  Filter   :  both
>> +  Index/Key:  1
>> +  Sources  :  None attached
>> +mirror3:
>> +  Type     :  gre
>> +  Sink     :  10.10.10.3
>> +  Filter   :  to-lport
>> +  Index/Key:  2
>> +  Sources  :  sw0-port1
>> +])
>> +
>> +dnl Delete a single mirror which has source attached.
>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
>> +
>> +dnl Check if the detach happened from source properly
>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules |  cut -b 3], [0], [dnl
>> +
>> +])
>> +
>> +dnl Check if the mirror deleted properly
>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> +mirror1:
>> +  Type     :  gre
>> +  Sink     :  10.10.10.1
>> +  Filter   :  from-lport
>> +  Index/Key:  0
>> +  Sources  :  None attached
>> +mirror2:
>> +  Type     :  erspan
>> +  Sink     :  10.10.10.2
>> +  Filter   :  both
>> +  Index/Key:  1
>> +  Sources  :  None attached
>> +])
>> +
>> +dnl Delete another mirror
>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
>> +
>> +dnl Update the Sink address
>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>> +
>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> +mirror1:
>> +  Type     :  gre
>> +  Sink     :  192.168.1.13
>> +  Filter   :  from-lport
>> +  Index/Key:  0
>> +  Sources  :  None attached
>> +])
>> +
>> +dnl Delete all mirrors
>> +AT_CHECK([ovn-nbctl mirror-del])
>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> +])])
>> +
>> +dnl ---------------------------------------------------------------------
>> +
>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>>   AT_CHECK([ovn-nbctl lr-add lr0])
>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index 4f399eccb..4e6c268e4 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>>   AT_CLEANUP
>>   ])
>>
>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> +AT_SETUP([Check NB-SB mirrors sync])
>> +AT_KEYWORDS([mirrors])
>> +ovn_start
>> +
>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> +"10.10.10.2"
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> +erspan
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> +0
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> +both
>> +])
>> +
>> +check ovn-nbctl --wait=sb \
>> +    -- set mirror . sink=192.168.1.13
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> +"192.168.1.13"
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> +erspan
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> +0
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> +both
>> +])
>> +
>> +check ovn-nbctl --wait=sb \
>> +    -- set mirror . type=gre
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> +gre
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> +"192.168.1.13"
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> +0
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> +both
>> +])
>> +
>> +check ovn-nbctl --wait=sb \
>> +    -- set mirror . index=12
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> +12
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> +gre
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> +"192.168.1.13"
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> +both
>> +])
>> +
>> +check ovn-nbctl --wait=sb \
>> +    -- set mirror . filter=to-lport
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> +to-lport
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> +12
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> +gre
>> +])
>> +
>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> +"192.168.1.13"
>> +])
>> +
>> +AT_CLEANUP
>> +])
>> +
>>   OVN_FOR_EACH_NORTHD_NO_HV([
>>   AT_SETUP([ACL skip hints for stateless config])
>>   AT_KEYWORDS([acl])
>> diff --git a/tests/ovn.at b/tests/ovn.at
>> index f8b8db4df..cd5527ea1 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>>   AT_CLEANUP
>>   ])
>>
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror])
>> +AT_KEYWORDS([Mirror])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
>> +    ofport-request=1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
>> +    ofport-request=1
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +ovn-nbctl dump-flows > sbflows
>> +AT_CAPTURE_FILE([sbflows])
>> +
>> +for i in 1 2; do
>> +    : > vif$i.expected
>> +done
>> +
>> +net_add n2
>> +
>> +sim_add hv2
>> +as hv2
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
>> +ovn_attach n2 br-phys 192.168.1.12
>> +
>> +OVN_POPULATE_ARP
>> +
>> +as hv1
>> +
>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
>> +#
>> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
>> +# provided, then it should be the ip and icmp checksums of the packet
>> +# responded; otherwise, no reply is expected.
>> +# In the absence of an ip checksum calculation helpers, this relies
>> +# on the caller to provide the checksums for the ip and icmp headers.
>> +# XXX This should be more systematic.
>> +#
>> +# INPORT is an lport number, e.g. 11 for vif11.
>> +# ETH_SRC and ETH_DST are each 12 hex digits.
>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>> +# ENCAP_TYPE - gre or erspan
>> +# FILTER - Mirror Filter - to-lport / from-lport
>> +test_ipv4_icmp_request() {
>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
>> +    shift; shift; shift; shift; shift; shift; shift
>> +    shift; shift; shift; shift;
>> +
>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>> +    local ip_ttl=02
>> +    local icmp_id=5fbf
>> +    local icmp_seq=0001
>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>> +    local icmp_type_code_request=0800
>> +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>> +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>> +
>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
>> +
>> +    # Expect to receive the reply, if any. In same port where packet was sent.
>> +    # Note: src and dst fields are expected to be reversed.
>> +    local icmp_type_code_response=0000
>> +    local reply_icmp_ttl=fe
>> +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>> +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>> +    echo $reply >> vif$inport.expected
>> +    local remote_mac=000000020200
>> +    local local_mac=000000010200
>> +    if test ${mirror_encap_type} = "gre" ; then
>> +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>> +        if test ${mirror_filter} = "to-lport" ; then
>> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>> +        elif test ${mirror_filter} = "from-lport" ; then
>> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>> +        fi
>> +    elif test ${mirror_encap_type} = "erspan" ; then
>> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>> +        local erspan_seq0=100088be000000001000000000000000
>> +        local erspan_seq1=100088be000000011000000000000000
>> +        if test ${mirror_filter} = "to-lport" ; then
>> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
>> +        elif test ${mirror_filter} = "from-lport" ; then
>> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>> +        fi
>> +    fi
>> +    echo $mirror >> br-phys_n1.expected
>> +
>> +}
>> +
>> +# Set IPs
>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>> +l1_ip=$(ip_to_hex 192 168 1 2)
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +
>> +# Send ping packet and check for mirrored packet of the reply
>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>> +
>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> +
>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> +rm -f br-phys_n1.expected
>> +rm -f vif1.expected
>> +
>> +check ovn-nbctl set mirror . type=erspan
>> +
>> +# Send ping packet and check for mirrored packet of the reply
>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>> +
>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> +
>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> +rm -f br-phys_n1.expected
>> +rm -f vif1.expected
>> +
>> +check ovn-nbctl set mirror . filter=from-lport
>> +
>> +# Send ping packet and check for mirrored packet of the request
>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>> +
>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> +
>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> +rm -f br-phys_n1.expected
>> +rm -f vif1.expected
>> +
>> +check ovn-nbctl set mirror . type=gre
>> +
>> +# Send ping packet and check for mirrored packet of the request
>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>> +
>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> +
>> +echo "---------OVN NB Mirror-----"
>> +ovn-nbctl mirror-list
>> +
>> +echo "---------OVS Mirror----"
>> +ovs-vsctl list Mirror
>> +
>> +echo "-----------------------"
>> +
>> +echo "Verifying Mirror deletion in OVS"
>> +# Set vif1 iface-id such that OVN releases port binding
>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>> +check ovn-nbctl --wait=hv sync
>> +
>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>> +])
>> +
>> +# Set vif1 iface-id back to ls1-lp1
>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
>> +check ovn-nbctl --wait=hv sync
>> +
>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
>> +
>> +# Delete vif1 so that OVN releases port binding
>> +check ovs-vsctl del-port br-int vif1
>> +check ovn-nbctl --wait=hv sync
>> +
>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>> +
>> +OVN_CLEANUP([hv1], [hv2])
>> +AT_CLEANUP
>> +])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror test bulk swap attachments])
>> +AT_KEYWORDS([Mirror test bulk swap attachments])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls1-lp2 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +# Create logical port ls2-lp2 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> +
>> +ovs-vsctl -- add-port br-int vif4 -- \
>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> +
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +# Equal detaches and attaches
>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> +
>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>> +
>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror test bulk attach multiple])
>> +AT_KEYWORDS([Mirror test bulk attach multiple])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls1-lp2 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +# Create logical port ls2-lp2 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> +
>> +ovs-vsctl -- add-port br-int vif4 -- \
>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> +
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +check ovn-nbctl mirror-del
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +
>> +# Attaches multiple mirrors
>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>> +
>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror test bulk more detach and less attach])
>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls1-lp2 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +# Create logical port ls2-lp2 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> +
>> +ovs-vsctl -- add-port br-int vif4 -- \
>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl --wait=hv sync
>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl --wait=hv sync
>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +check ovn-nbctl mirror-del
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +# Detaches more than attaches
>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
>> +
>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror test bulk attach more than detach])
>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls1-lp2 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +# Create logical port ls2-lp2 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> +
>> +ovs-vsctl -- add-port br-int vif4 -- \
>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> +
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> +check ovn-nbctl --wait=hv sync
>> +
>> +# Attaches more than detaches
>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +as hv1
>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> +
>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>> +
>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>> +
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([Mirror test bulk detach multiple])
>> +AT_KEYWORDS([Mirror test bulk detach multiple])
>> +ovn_start
>> +
>> +# Logical network:
>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> +
>> +ovn-nbctl lr-add R1
>> +
>> +ovn-nbctl ls-add ls1
>> +ovn-nbctl ls-add ls2
>> +
>> +# Connect ls1 to R1
>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>> +
>> +# Connect ls2 to R1
>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>> +
>> +# Create logical port ls1-lp1 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> +
>> +# Create logical port ls1-lp2 in ls1
>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> +
>> +# Create logical port ls2-lp1 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> +
>> +# Create logical port ls2-lp2 in ls2
>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> +
>> +ovn-nbctl lsp-add ls1 ln-public
>> +ovn-nbctl lsp-set-type ln-public localnet
>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> +
>> +# Create one hypervisor and create OVS ports corresponding to logical ports.
>> +net_add n1
>> +
>> +sim_add hv1
>> +as hv1
>> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>> +ovn_attach n1 br-phys 192.168.1.11
>> +
>> +ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> +
>> +ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> +
>> +ovs-vsctl -- add-port br-int vif4 -- \
>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> +
>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> +
>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> +
>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> +
>> +check ovn-nbctl --wait=hv sync
>> +
>> +# Detaches all
>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> +check ovn-nbctl --wait=hv sync
>> +
>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>>
>>   OVN_FOR_EACH_NORTHD([
>>   AT_SETUP([Port Groups])
>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>> index 811468dc6..af2e61435 100644
>> --- a/utilities/ovn-nbctl.c
>> +++ b/utilities/ovn-nbctl.c
>> @@ -271,6 +271,19 @@ QoS commands:\n\
>>                               remove QoS rules from SWITCH\n\
>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>>   \n\
>> +Mirror commands:\n\
>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
>> +                            add a mirror with given name\n\
>> +                            specify TYPE 'gre' or 'erspan'\n\
>> +                            specify the tunnel INDEX value\n\
>> +                                (indicates key if GRE\n\
>> +                                 erpsan_idx if ERSPAN)\n\
>> +                            specify FILTER for mirroring selection\n\
>> +                                'to-lport' / 'from-lport' / 'both'\n\
>> +                            specify Sink / Destination i.e. Remote IP\n\
>> +  mirror-del [NAME]         remove mirrors\n\
>> +  mirror-list               print mirrors\n\
>> +\n\
>>   Meter commands:\n\
>>     [--fair]\n\
>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>>                               set dhcpv6 options for PORT\n\
>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>>     lsp-get-ls PORT           get the logical switch which the port belongs to\n\
>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>>   \n\
>>   Forwarding group commands:\n\
>>     [--liveness]\n\
>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>>       ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>>   }
>>
>> +static void
>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>> +{
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>> +    ovsdb_idl_add_column(ctx->idl,
>> +                         &nbrec_logical_switch_port_col_mirror_rules);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> +}
>> +
>> +static int
>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
>> +{
>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>> +
>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
>> +
>> +    return strcmp(mirror1->name,mirror2->name);
>> +}
>> +
>> +static char * OVS_WARN_UNUSED_RESULT
>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>> +                    bool must_exist,
>> +                    const struct nbrec_mirror **mirror_p)
>> +{
>> +    const struct nbrec_mirror *mirror = NULL;
>> +    *mirror_p = NULL;
>> +
>> +    struct uuid mirror_uuid;
>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>> +    if (is_uuid) {
>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
>> +    }
>> +
>> +    if (!mirror) {
>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> +            if (!strcmp(mirror->name, id)) {
>> +                break;
>> +            }
>> +        }
>> +    }
>> +
>> +    if (!mirror && must_exist) {
>> +        return xasprintf("%s: mirror %s not found",
>> +                         id, is_uuid ? "UUID" : "name");
>> +    }
>> +
>> +    *mirror_p = mirror;
>> +    return NULL;
>> +}
>> +
>> +static void
>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>> +{
>> +    const char *port = ctx->argv[1];
>> +    const char *mirror_name = ctx->argv[2];
>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>> +    const struct nbrec_mirror *mirror;
>> +
>> +    char *error;
>> +
>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +
>> +    /*check if a mirror rule actually exists on that name or not*/
>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +    /* Check if same mirror rule already exists for the lsp */
>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>> +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
>> +            if (!may_exist) {
>> +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
>> +                          ctx->argv[1]);
>> +                return;
>> +            }
>> +            return;
>> +        }
>> +    }
>> +
>> +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
>> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
>> +
>> +}
>> +
>> +static void
>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>> +{
>> +    const char *port = ctx->argv[1];
>> +    const char *mirror_name = ctx->argv[2];
>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>> +    const struct nbrec_mirror *mirror;
>> +
>> +    char *error;
>> +
>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +
>> +    /*check if a mirror rule actually exists on that name or not*/
>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
>> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
>> +
>> +}
>> +
>>   static void
>>   nbctl_lsp_set_type(struct ctl_context *ctx)
>>   {
>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
>>   }
>>
>> +static char * OVS_WARN_UNUSED_RESULT
>> +parse_filter(const char *arg, const char **selection_p)
>> +{
>> +    /* Validate selection.  Only require the first letter. */
>> +    if (arg[0] == 't') {
>> +        *selection_p = "to-lport";
>> +    } else if (arg[0] == 'f') {
>> +        *selection_p = "from-lport";
>> +    } else if (arg[0] == 'b') {
>> +        *selection_p = "both";
>> +    } else {
>> +        *selection_p = NULL;
>> +        return xasprintf("%s: selection must be \"to-lport\" or "
>> +                         "\"from-lport\" or \"both\" ", arg);
>> +    }
>> +    return NULL;
>> +}
>> +
>> +static char * OVS_WARN_UNUSED_RESULT
>> +parse_type(const char *arg, const char **type_p)
>> +{
>> +    /* Validate type.  Only require the first letter. */
>> +    if (arg[0] == 'g') {
>> +        *type_p = "gre";
>> +    } else if (arg[0] == 'e') {
>> +        *type_p = "erspan";
>> +    } else {
>> +        *type_p = NULL;
>> +        return xasprintf("%s: type must be \"gre\" or "
>> +                         "\"erspan\"", arg);
>> +    }
>> +    return NULL;
>> +}
>> +
>> +static void
>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
>> +{
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> +}
>> +
>> +static void
>> +nbctl_mirror_add(struct ctl_context *ctx)
>> +{
>> +    const char *filter = NULL;
>> +    const char *sink_ip = NULL;
>> +    const char *type = NULL;
>> +    const char *name = NULL;
>> +    char *new_sink_ip = NULL;
>> +    int64_t index;
>> +    char *error = NULL;
>> +    const struct nbrec_mirror *mirror_check = NULL;
>> +
>> +    /* Mirror Name */
>> +    name = ctx->argv[1];
>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>> +        if (!strcmp(mirror_check->name, name)) {
>> +            ctl_error(ctx, "Mirror with %s name already exists.",
>> +                      name);
>> +            return;
>> +        }
>> +    }
>> +
>> +    /* Tunnel Type - GRE/ERSPAN */
>> +    error = parse_type(ctx->argv[2], &type);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +    /* tunnel index / GRE key / ERSPAN idx */
>> +    index = atoi(ctx->argv[3]);
> Shouldn't we validate the input is an actual number?
>
>> +
>> +    /* Filter for mirroring */
>> +    error = parse_filter(ctx->argv[4], &filter);
>> +    if (error) {
>> +        ctx->error = error;
>> +        return;
>> +    }
>> +
>> +    /* Destination / Sink details */
>> +    sink_ip = ctx->argv[5];
>> +
>> +    /* check if it is a valid ip */
>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>> +    if (!new_sink_ip) {
>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>> +    }
>> +
>> +    if (new_sink_ip) {
>> +        free(new_sink_ip);
>> +    } else {
>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>> +        return;
>> +    }
>> +
>> +    /* Create the mirror. */
>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>> +    nbrec_mirror_set_name(mirror, name);
>> +    nbrec_mirror_set_index(mirror, index);
>> +    nbrec_mirror_set_filter(mirror, filter);
>> +    nbrec_mirror_set_type(mirror, type);
>> +    nbrec_mirror_set_sink(mirror, sink_ip);
>> +
>> +}
>> +
>> +static void
>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
>> +{
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> +}
>> +
>> +static void
>> +nbctl_mirror_del(struct ctl_context *ctx)
>> +{
>> +    const struct nbrec_mirror *mirror, *next;
>> +
>> +    /* If a name is not specified, delete all mirrors. */
>> +    if (ctx->argc == 1) {
>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>> +            nbrec_mirror_delete(mirror);
>> +        }
>> +        return;
>> +    }
>> +
>> +    /* Remove the matching mirror. */
>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> +        if (strcmp(ctx->argv[1], mirror->name)) {
>> +            continue;
>> +        }
>> +        nbrec_mirror_delete(mirror);
>> +        return;
>> +    }
>> +}
>> +
>> +static void
>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
>> +{
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> +}
>> +
>> +static void
>> +nbctl_mirror_list(struct ctl_context *ctx)
>> +{
>> +
>> +    const struct nbrec_mirror **mirrors = NULL;
>> +    const struct nbrec_mirror *mirror;
>> +    size_t n_capacity = 0;
>> +    size_t n_mirrors = 0;
>> +
>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> +        if (n_mirrors == n_capacity) {
>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
>> +        }
>> +
>> +        mirrors[n_mirrors] = mirror;
>> +        n_mirrors++;
>> +    }
>> +
>> +    if (n_mirrors) {
>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>> +    }
>> +
>> +    for (size_t i = 0; i < n_mirrors; i++) {
>> +        mirror = mirrors[i];
>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>> +        /* print all the values */
>> +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
>> +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
>> +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
>> +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
>> +                                                (long int) mirror->index);
> You don't ned to cast if you pass %d formatter instead of %ld. The same applies to other places in the patch where you cast to long int. In general, casting is not needed and should be avoided.
>
>> +        ds_put_cstr(&ctx->output,   "  Sources  :");
>> +        if (mirror->n_src > 0) {
>> +            struct svec srcs;
>> +            const char *src;
>> +            size_t j;
>> +            svec_init(&srcs);
>> +            for (j = 0; j < mirror->n_src; j++) {
>> +                svec_add(&srcs, mirror->src[j]->name);
>> +            }
>> +            svec_sort(&srcs);
>> +            SVEC_FOR_EACH (j, src, &srcs) {
>> +                ds_put_format(&ctx->output, "  %s", src);
>> +            }
>> +            svec_destroy(&srcs);
>> +        } else {
>> +            ds_put_cstr(&ctx->output, "  None attached");
>> +        }
>> +        ds_put_cstr(&ctx->output, "\n");
>> +    }
>> +
>> +    free(mirrors);
>> +}
>> +
>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
>>       = {{&nbrec_logical_switch_port_col_name, NULL,
>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>>         NULL, "", RO },
>>
>> +    /* mirror commands. */
>> +    { "mirror-add", 5, 5,
>> +      "NAME TYPE INDEX FILTER IP",
>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
>> +    { "mirror-del", 0, 1, "[NAME]",
>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
>> +      NULL, "", RO },
>> +
>>       /* meter commands. */
>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>>         NULL, "", RO },
>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>>
>>       /* forwarding group commands. */
>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>> index f60dde1b6..3d73e9e25 100644
>> --- a/utilities/ovn-sbctl.c
>> +++ b/utilities/ovn-sbctl.c
>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>> +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>>
>>       ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>>       ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>>
>> +    [SBREC_TABLE_MIRROR].row_ids[0]
>> +    = {&sbrec_mirror_col_name, NULL, NULL},
>> +
>>       [SBREC_TABLE_METER].row_ids[0]
>>       = {&sbrec_meter_col_name, NULL, NULL},
>>
>> --
>> 2.31.1
>>
Ihar Hrachyshka Nov. 15, 2022, 11:09 p.m. UTC | #3
On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
> I think there's a problem with the bulk tests added in this patch. I 
> will cover this issue in this email, and I'll send my code review 
> tomorrow as promised, since it's getting late here.
>
>
> When running the whole suite locally, I get the following failures:
>
>
> 401: Mirror test bulk swap attachments -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425)
> 402: Mirror test bulk swap attachments -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425)
> 403: Mirror test bulk attach multiple -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537)
> 410: Mirror test bulk more detach and less attach -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=no ok
> 412: Mirror test bulk attach more than detach -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=no ok
> 416: Mirror test bulk detach multiple -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=no ok
> 408: Mirror test bulk more detach and less attach -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650)
> 417: Mirror test bulk detach multiple -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=yes ok
> 409: Mirror test bulk more detach and less attach -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650)
> 411: Mirror test bulk attach more than detach -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> 418: Mirror test bulk detach multiple -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=no ok
> 413: Mirror test bulk attach more than detach -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> 415: Mirror test bulk detach multiple -- ovn-northd -- 
> parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879)
> 414: Mirror test bulk attach more than detach -- ovn-northd -- 
> parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766)
>
>
> Note that some of the test case variants passed, and I don't think 
> there's a clear pattern as to which of variants in the test matrix do 
> fail.
>
>
> The error that triggers the failure is during ovs-vswitchd cleanup:
>
>
> ./ovn.at:16425: ovs-appctl --timeout=10 -t ovs-vswitchd exit --cleanup
> --- /dev/null   2022-11-04 04:09:25.869645998 +0000
> +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr 
> 2022-11-15 16:31:15.557479369 +0000
> @@ -0,0 +1,2 @@
> +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with signal 
> 14 (Alarm clock)
> +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source: line 
> 282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t 
> ovs-vswitchd exit --cleanup
> ./ovn.at:16425: exit code was 142, expected 0
>
>
> The very last message in ovs-vswitchd log on hv1 is exactly 10 seconds 
> before the alarm clock error:
>
>
> 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max 
> translation depth 64 on bridge br-int while processing 
> arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>
>
> I don't see coredumps generated for any of test processes, so it's 
> probably not the case of ovs-vswitchd crashing on exit request.
>
>
> I tried to adjust your test cases to a minimal reproducer and I found 
> that if a test case creates two mirrors, both of to-lport type, then 
> ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl 
> requests, nor it handles new ports. But if I merely change the type of 
> one of mirrors in the test to from-lport, the test passes.
>
>
> On the other hand, a consistent way to trigger the failure is adding a 
> 'sleep 3' at the end of a test case just before cleanup, apparently to 
> allow vswitchd to catch on the mirror updates and lock somewhere in 
> the code. I see vswitchd spinning at ~100% cpu in 'top' output when it 
> gets into this state. It's clearly doing SOMETHING, not just sleeping. :)
>
>
> I suspect there's some bug inside vswitchd that makes it lock / spin 
> for a particular setup of mirrors. Whatever OVN sets up in vswitchd 
> database, the latter should not freeze. It would be helpful to provide 
> a short ovs-only reproducer for the situation that would not involve 
> OVN so that our OVS friends can take a look.
>
>
> For the record, the mirrors in ovsdb are:
>
>
> _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
> external_ids        : {}
> name                : mirror0
> output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
> output_vlan         : []
> select_all          : false
> select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab, 
> 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
> select_src_port     : []
> select_vlan         : []
> snaplen             : []
> statistics          : {}
>
> _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
> external_ids        : {}
> name                : mirror1
> output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
> output_vlan         : []
> select_all          : false
> select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6, 
> 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
> select_src_port     : []
> select_vlan         : []
> snaplen             : []
> statistics          : {}
>
>
> Bridge output here:
>
>
> 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
>     Bridge br-int
>         fail_mode: secure
>         datapath_type: system
>         Port vif4
>             Interface vif4
>         Port vif2
>             Interface vif2
>         Port br-int
>             Interface br-int
>                 type: internal
>         Port vif1
>             Interface vif1
>         Port ovn-mirror0
>             Interface ovn-mirror0
>                 type: gre
>                 options: {key="0", remote_ip="192.168.1.12"}
>         Port vif3
>             Interface vif3
>         Port ovn-mirror1
>             Interface ovn-mirror1
>                 type: gre
>                 options: {key="1", remote_ip="192.168.1.12"}
>         Port patch-br-int-to-ln-public
>             Interface patch-br-int-to-ln-public
>                 type: patch
>                 options: {peer=patch-ln-public-to-br-int}
>     Bridge br-phys
>         Port br-phys
>             Interface br-phys
>                 type: internal
>                 options: 
> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap", 
> tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
>         Port br-phys_n1
>             Interface br-phys_n1
>                 options: 
> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap", 
> stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock", 
> tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"} 
>
>         Port patch-ln-public-to-br-int
>             Interface patch-ln-public-to-br-int
>                 type: patch
>                 options: {peer=patch-br-int-to-ln-public}
>
Perhaps this may be of relevance: when I attach gdb to vswitchd process, 
I can see a never-ending stack of calls in thread 1 that looks like:

#0  0x0000000000406760 in memcpy@plt ()
#1  0x0000000000499d88 in dp_packet_clone_with_headroom 
(buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
#2  0x000000000049bd4d in dp_packet_batch_clone (dst=0x7fffcbf54740, 
src=0x7fffcbf55230) at lib/dp-packet.h:863
#3  0x00000000004b15ef in dp_execute_output_action (pmd=0x7f638115f010, 
packets_=0x7fffcbf55230, should_steal=false, port_no=3) at 
lib/dpif-netdev.c:8696
#4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200, 
packets_=0x7fffcbf55230, a=0x7fffcbf555c0, should_steal=false) at 
lib/dpif-netdev.c:8787
#5  0x0000000000507834 in odp_execute_actions (dp=0x7fffcbf55200, 
batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578, 
actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
     at lib/odp-execute.c:993
#6  0x00000000004b251e in dp_netdev_execute_actions (pmd=0x7f638115f010, 
packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60, 
actions=0x7fffcbf55578, actions_len=88)
     at lib/dpif-netdev.c:9105
#7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630, 
execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
#8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630, 
ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at 
lib/dpif-netdev.c:4606
#9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630, 
ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at 
lib/dpif.c:1372
#10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630, 
execute=0x7fffcbf55500) at lib/dpif.c:1326
#11 0x000000000043e46d in ofproto_dpif_execute_actions__ 
(ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0, 
ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
     packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
#12 0x0000000000467e8a in compose_table_xlate (ctx=0x7fffcbf5f240, 
out_dev=0x1879430, packet=0x7fffcbf56050) at 
ofproto/ofproto-dpif-xlate.c:3526
#13 0x0000000000468020 in tnl_send_arp_request (ctx=0x7fffcbf5f240, 
out_dev=0x1879430, eth_src=..., ip_src=184658112, ip_dst=201435328) at 
ofproto/ofproto-dpif-xlate.c:3555
#14 0x0000000000468710 in native_tunnel_output (ctx=0x7fffcbf5f240, 
xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7, truncate=false, 
is_last_action=false)
     at ofproto/ofproto-dpif-xlate.c:3721
#15 0x000000000046a78e in compose_output_action__ (ctx=0x7fffcbf5f240, 
ofp_port=7, xr=0x0, check_stp=true, is_last_action=false, 
truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
#16 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240, 
ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at 
ofproto/ofproto-dpif-xlate.c:4416
#17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240, 
out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at 
ofproto/ofproto-dpif-xlate.c:2533
#18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240, 
xbundle=0x1880d00, mirrors=2) at ofproto/ofproto-dpif-xlate.c:2190
#19 0x000000000046a96b in compose_output_action__ (ctx=0x7fffcbf5f240, 
ofp_port=1, xr=0x0, check_stp=true, is_last_action=false, 
truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
#20 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240, 
ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at 
ofproto/ofproto-dpif-xlate.c:4416
#21 0x000000000046cf3e in xlate_output_action (ctx=0x7fffcbf5f240, 
port=1, controller_len=0, may_packet_in=true, is_last_action=false, 
truncate=false, group_bucket_action=false)
     at ofproto/ofproto-dpif-xlate.c:5361
#22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638, 
ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false, 
group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
#23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240, 
rule=0x1860480, deepens=false, is_last_action=false, 
actions_xlator=0x470caf <do_xlate_actions>)
     at ofproto/ofproto-dpif-xlate.c:4439
#24 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240, 
in_port=5, table_id=65 'A', may_packet_in=false, honor_table_miss=false, 
with_ct_orig=false, is_last_action=false,
     xlator=0x470caf <do_xlate_actions>) at 
ofproto/ofproto-dpif-xlate.c:4568
#25 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240, 
resubmit=0x185cfb8, is_last_action=false) at 
ofproto/ofproto-dpif-xlate.c:4879
#26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8, 
ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false, 
group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
#27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240, 
rule=0x185ce00, deepens=false, is_last_action=false, 
actions_xlator=0x470caf <do_xlate_actions>)
     at ofproto/ofproto-dpif-xlate.c:4439
#28 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240, 
in_port=5, table_id=64 '@', may_packet_in=false, honor_table_miss=false, 
with_ct_orig=false, is_last_action=false,
     xlator=0x470caf <do_xlate_actions>) at 
ofproto/ofproto-dpif-xlate.c:4568
#29 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240, 
resubmit=0x1834458, is_last_action=false) at 
ofproto/ofproto-dpif-xlate.c:4879
#30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458, 
ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false, 
group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173

it goes like that over and over and over for hundreds if not thousands 
of calls down the stack until

#5738 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330, 
in_port=65533, table_id=9 '\t', may_packet_in=false, 
honor_table_miss=false, with_ct_orig=false, is_last_action=true, 
xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
#5739 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330, 
resubmit=0x1831ac0, is_last_action=true) at 
ofproto/ofproto-dpif-xlate.c:4879
#5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68, 
ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true, 
group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
#5741 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcc09b330, 
rule=0x18318b0, deepens=false, is_last_action=true, 
actions_xlator=0x470caf <do_xlate_actions>) at 
ofproto/ofproto-dpif-xlate.c:4439
#5742 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330, 
in_port=65533, table_id=8 '\b', may_packet_in=false, 
honor_table_miss=false, with_ct_orig=false, is_last_action=true, 
xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
#5743 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330, 
resubmit=0x187bcc0, is_last_action=true) at 
ofproto/ofproto-dpif-xlate.c:4879
#5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80, 
ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true, 
group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
#5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0, 
xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
#5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0, 
opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
#5747 0x00000000004250d7 in ofproto_packet_out_start (ofproto=0x17c10e0, 
opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
#5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40, 
oh=0x17fdfa0) at ofproto/ofproto.c:3764
#5749 0x000000000042fce2 in handle_single_part_openflow 
(ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at 
ofproto/ofproto.c:8664
#5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40, 
msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
#5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40, 
handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:1329
#5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0, 
handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:356
#5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at 
ofproto/ofproto.c:1933
#5754 0x00000000004101d4 in bridge_run__ () at vswitchd/bridge.c:3210
#5755 0x00000000004103d3 in bridge_run () at vswitchd/bridge.c:3269
#5756 0x0000000000415cf0 in main (argc=10, argv=0x7fffcc09dff8) at 
vswitchd/ovs-vswitchd.c:129

The main thread never getting out of some processing code to reach any 
other handlers (e.g. for appctl requests?)

I'm adding Ilya to CC in case he has an idea why vswitchd could lock / 
freeze / spin indefinitely on two to-lport mirror creation.


> On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
>> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>>> Mirror creation just creates the mirror. The lsp-attach-mirror
>>> triggers the sequence to create Mirror in OVS DB on compute node.
>>> OVS already supports Port Mirroring.
>>>
>>> Note: This is targeted to mirror to destinations anywhere outside the
>>> cluster where the analyser resides and it need not be an OVN node.
>>>
>>> Example commands are as below:
>>>
>>> Mirror creation
>>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>>>
>>> Attach a logical port to the mirror.
>>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>>>
>>> Detach a source from Mirror
>>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>>>
>>> Mirror deletion
>>> ovn-nbctl mirror-del mirror1
>>>
>>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
>>> ---
>>> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
>>>               test to make it pass consistently.
>>> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
>>>
>>> V10 --> V11: Addressed review comments from V10 by Ihar
>>>             i) Expanded bulk updates test cases in ovn.at
>>>                Overall below cases are covered
>>>                a) Attaches multiple mirrors (new)
>>>                b) Equal detaches and attaches (same as V10)
>>>                c) Detaches more than attaches (new)
>>>                d) Attaches more than detaches (new)
>>>                e) Detaches all (new)
>>>            ii) Addressed the detach all case in mirror.c
>>>           iii) Minor correction in NEWS
>>>            iv) Added invalid mirror attach case in ovn-nbctl.at
>>>
>>> Files modified (V10 --> V11):
>>> Code --> mirror.c
>>> Test --> ovn.at, ovn-nbctl.at
>>> Misc --> NEWS
>>>
>>> Ihar,
>>>      Regarding mirror_delete function param delete_all it is wrt the
>>> port binding and if a port binding is removed we delete all its
>>> attachment. Already that use case is covered in ovn.at.
>>> Having said that the detaches all had issue in mirror_delete which
>>> I have addressed. With all the above cases added now in bulk updates
>>> hope it should give good assurance.
>>>
>>>   NEWS                        |   1 +
>>>   controller/automake.mk      |   4 +-
>>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>>>   controller/mirror.h         |  53 +++
>>>   controller/ovn-controller.c | 266 ++++++++++--
>>>   northd/en-northd.c          |   4 +
>>>   northd/inc-proc-northd.c    |   4 +
>>>   northd/northd.c             | 172 ++++++++
>>>   northd/northd.h             |   2 +
>>>   ovn-nb.ovsschema            |  31 +-
>>>   ovn-nb.xml                  |  63 +++
>>>   ovn-sb.ovsschema            |  26 +-
>>>   ovn-sb.xml                  |  50 +++
>>>   tests/ovn-nbctl.at          | 120 ++++++
>>>   tests/ovn-northd.at         | 102 +++++
>>>   tests/ovn.at                | 778 
>>> ++++++++++++++++++++++++++++++++++++
>>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>>>   utilities/ovn-sbctl.c       |   4 +
>>>   18 files changed, 2547 insertions(+), 28 deletions(-)
>>>   create mode 100644 controller/mirror.c
>>>   create mode 100644 controller/mirror.h
>>>
>>> diff --git a/NEWS b/NEWS
>>> index 224a7b83e..84b22abdb 100644
>>> --- a/NEWS
>>> +++ b/NEWS
>>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>>>       any of LR's LRP IP, there is no need to create SNAT entry.  
>>> Now such
>>>       traffic destined to LRP IP is not dropped.
>>>     - Bump python version required for building OVN to 3.6.
>>> +  - Added Support for Remote Port Mirroring.
>>>
>>>   OVN v22.06.0 - 03 Jun 2022
>>>   --------------------------
>>> diff --git a/controller/automake.mk b/controller/automake.mk
>>> index c2ab1bbe6..334672b4d 100644
>>> --- a/controller/automake.mk
>>> +++ b/controller/automake.mk
>>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>>>          controller/ovsport.h \
>>>          controller/ovsport.c \
>>>          controller/vif-plug.h \
>>> -       controller/vif-plug.c
>>> +       controller/vif-plug.c \
>>> +       controller/mirror.h \
>>> +       controller/mirror.c
>>>
>>>   controller_ovn_controller_LDADD = lib/libovn.la 
>>> $(OVS_LIBDIR)/libopenvswitch.la
>>>   man_MANS += controller/ovn-controller.8
>>> diff --git a/controller/mirror.c b/controller/mirror.c
>>> new file mode 100644
>>> index 000000000..11f2b63a6
>>> --- /dev/null
>>> +++ b/controller/mirror.c
>>> @@ -0,0 +1,538 @@
>>> +/* Copyright (c) 2022 Red Hat, Inc.
>>> + *
>>> + * Licensed under the Apache License, Version 2.0 (the "License");
>>> + * you may not use this file except in compliance with the License.
>>> + * You may obtain a copy of the License at:
>>> + *
>>> + *     http://www.apache.org/licenses/LICENSE-2.0
>>> + *
>>> + * Unless required by applicable law or agreed to in writing, software
>>> + * distributed under the License is distributed on an "AS IS" BASIS,
>>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
>>> implied.
>>> + * See the License for the specific language governing permissions and
>>> + * limitations under the License.
>>> + */
>>> +
>>> +#include <config.h>
>>> +#include <unistd.h>
>>> +
>>> +/* library headers */
>>> +#include "lib/sset.h"
>>> +#include "lib/util.h"
>>> +
>>> +/* OVS includes. */
>>> +#include "lib/vswitch-idl.h"
>>> +#include "openvswitch/vlog.h"
>>> +
>>> +/* OVN includes. */
>>> +#include "binding.h"
>>> +#include "lib/ovn-sb-idl.h"
>>> +#include "mirror.h"
>>> +
>>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
>>> +
>>> +/* Static function declarations */
>>> +
>>> +static const struct ovsrec_port *
>>> +get_port_for_iface(const struct ovsrec_interface *iface,
>>> +                  const struct ovsrec_bridge *br_int)
>>> +{
>>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
>>> +        const struct ovsrec_port *p = br_int->ports[i];
>>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
>>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
>>> +                return p;
>>> +            }
>>> +        }
>>> +    }
>>> +    return NULL;
>>> +}
>>> +
>>> +static bool
>>> +mirror_create(const struct sbrec_port_binding *pb,
>>> +              struct port_mirror_ctx *pm_ctx)
>>> +{
>>> +    const struct ovsrec_mirror *mirror = NULL;
>>> +
>>> +    if (pb->n_up && !pb->up[0]) {
>>> +        return true;
>>> +    }
>>> +
>>> +    if (pb->chassis != pm_ctx->chassis_rec) {
>>> +        return true;
>>> +    }
>>> +
>>> +    if (!pm_ctx->ovs_idl_txn) {
>>> +        return false;
>>> +    }
>>> +
>>> +
>>> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>>> +    /* Loop through the mirror rules */
>>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>>> +        /* check if the mirror already exists in OVS DB */
>>> +        bool create_mirror = true;
>>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>>> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>>> +                /* Mirror with same name already exists
>>> +                 * No need to create mirror
>>> +                 */
>>> +                create_mirror = false;
>>> +                break;
>>> +            }
>>> +        }
>>> +
>>> +        if (create_mirror) {
>>> +
>>> +            struct smap options = SMAP_INITIALIZER(&options);
>>> +            char *port_name, *key;
>>> +
>>> +            key = xasprintf("%ld",(long int) 
>>> pb->mirror_rules[i]->index);
>>> +            smap_add(&options, "remote_ip", 
>>> pb->mirror_rules[i]->sink);
>>> +            smap_add(&options, "key", key);
>>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>>> +                /* Set the ERSPAN index */
>>> +                smap_add(&options, "erspan_idx", key);
>>> +                smap_add(&options, "erspan_ver","1");
>>> +
>>> +            }
>>> +            struct ovsrec_interface *iface =
>>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>>> +            port_name = xasprintf("ovn-%s",
>>> + pb->mirror_rules[i]->name);
>>> +
>>> +            ovsrec_interface_set_name(iface, port_name);
>>> +            ovsrec_interface_set_type(iface, 
>>> pb->mirror_rules[i]->type);
>>> +            ovsrec_interface_set_options(iface, &options);
>>> +
>>> +            struct ovsrec_port *port =
>>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>>> +            ovsrec_port_set_name(port, port_name);
>>> +            ovsrec_port_set_interfaces(port, &iface, 1);
>>> +
>>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>>> +
>>> +            smap_destroy(&options);
>>> +            free(port_name);
>>> +            free(key);
>>> +
>>> +            VLOG_INFO("Creating Mirror in OVS DB");
>>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>>> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
>>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>>> + mirror);
>>> +        }
>>> +
>>> +        struct local_binding *lbinding = local_binding_find(
>>> +                               pm_ctx->local_bindings, 
>>> pb->logical_port);
>>> +        const struct ovsrec_port *p =
>>> +                     get_port_for_iface(lbinding->iface, 
>>> pm_ctx->br_int);
>>> +        if (p) {
>>> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>> +            } else if 
>>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>> +            } else {
>>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>> +            }
>>> +        }
>>> +    }
>>> +    return true;
>>> +}
>>> +
>>> +static void
>>> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
>>> +                              struct ovsrec_mirror *ovs_mirror)
>>> +{
>>> +    char *filter;
>>> +    if ((ovs_mirror->n_select_dst_port)
>>> +            && (ovs_mirror->n_select_src_port)) {
>>> +        filter = "both";
>>> +    } else if (ovs_mirror->n_select_dst_port) {
>>> +        filter = "to-lport";
>>> +    } else {
>>> +        filter = "from-lport";
>>> +    }
>>> +
>>> +    if (strcmp(sb_mirror->filter, filter)) {
>>> +        if (!strcmp(sb_mirror->filter,"from-lport")
>>> +                              && !strcmp(filter,"both")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>> + ovs_mirror->select_dst_port[i]);
>>> +            }
>>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>> +                              && !strcmp(filter,"both")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>> + ovs_mirror->select_src_port[i]);
>>> +            }
>>> +        } else if (!strcmp(sb_mirror->filter,"both")
>>> +                              && !strcmp(filter,"from-lport")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>> + ovs_mirror->select_src_port[i]);
>>> +            }
>>> +        } else if (!strcmp(sb_mirror->filter,"both")
>>> +                              && !strcmp(filter,"to-lport")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>> + ovs_mirror->select_dst_port[i]);
>>> +            }
>>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>> +                              && !strcmp(filter,"from-lport")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>> + ovs_mirror->select_src_port[i]);
>>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>> + ovs_mirror->select_src_port[i]);
>>> +            }
>>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>>> +                              && !strcmp(filter,"to-lport")) {
>>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; 
>>> i++) {
>>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>> + ovs_mirror->select_dst_port[i]);
>>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>> + ovs_mirror->select_dst_port[i]);
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +static void
>>> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
>>> +                                   struct ovsrec_mirror *ovs_mirror)
>>> +{
>>> +    struct smap options = SMAP_INITIALIZER(&options);
>>> +    char *key, *type;
>>> +    struct ovsrec_interface *iface =
>>> + ovs_mirror->output_port->interfaces[0];
>>> +    struct smap *opts = &iface->options;
>>> +
>>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>>> +    if (erspan_ver) {
>>> +        type = "erspan";
>>> +    } else {
>>> +        type = "gre";
>>> +    }
>>> +    if (strcmp(type, sb_mirror->type)) {
>>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
>>> +    }
>>> +
>>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
>>> +    smap_add(&options, "key", key);
>>> +
>>> +    if (!strcmp(sb_mirror->type, "erspan")) {
>>> +        /* Set the ERSPAN index */
>>> +        smap_add(&options, "erspan_idx", key);
>>> +        smap_add(&options, "erspan_ver","1");
>>> +    }
>>> +
>>> +    ovsrec_interface_set_options(iface, &options);
>>> +    smap_destroy(&options);
>>> +    free(key);
>>> +
>>> +}
>>> +
>>> +static void
>>> +mirror_update(const struct sbrec_mirror *sb_mirror,
>>> +              struct ovsrec_mirror *ovs_mirror)
>>> +{
>>> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
>>> +
>>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>>> +}
>>> +
>>> +static bool
>>> +mirror_delete(const struct sbrec_port_binding *pb,
>>> +              struct port_mirror_ctx *pm_ctx,
>>> +              struct shash *pb_mirror_map,
>>> +              bool delete_all)
>>> +{
>>> +
>>> +    if (!pm_ctx->ovs_idl_txn) {
>>> +        return false;
>>> +    }
>>> +
>>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>>> +
>>> +    if (!delete_all) {
>>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>>> +        }
>>> +    }
>>> +
>>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) && 
>>> pb->n_mirror_rules) {
>>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>> +
>>> +            struct ovsrec_mirror *ovs_mirror = NULL;
>>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>>> + pb->mirror_rules[i]->name);
>>> +            if (ovs_mirror) {
>>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>> + ovs_mirror->output_port);
>>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>> + ovs_mirror);
>>> + ovsrec_port_delete(ovs_mirror->output_port);
>>> +                ovsrec_mirror_delete(ovs_mirror);
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    struct shash_node *mirror_node;
>>> +    const struct sbrec_port_binding *sb_pb;
>>> +    int attach_cnt = 0;
>>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>>> +            /* Find if the mirror has other sources */
>>> +            attach_cnt = 0;
>>> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>>> + pm_ctx->port_binding_table) {
>>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
>>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
>>> + ovs_mirror->name)) {
>>> +                        attach_cnt++;
>>> +                    }
>>> +                }
>>> +            }
>>> +            if (attach_cnt) {
>>> +                /* More than 1 source then just
>>> +                 * update the mirror table
>>> +                 */
>>> +                bool done = false;
>>> +                for (size_t i = 0; ((i < 
>>> ovs_mirror->n_select_dst_port)
>>> +                                                   && (done == 
>>> false)); i++) {
>>> +                    const struct ovsrec_port *port_rec =
>>> + ovs_mirror->select_dst_port[i];
>>> +                    for (size_t j = 0; j < port_rec->n_interfaces; 
>>> j++) {
>>> +                        const struct ovsrec_interface *iface_rec;
>>> +
>>> +                        iface_rec = port_rec->interfaces[j];
>>> +                        const char *iface_id =
>>> + smap_get(&iface_rec->external_ids,
>>> + "iface-id");
>>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>>> + ovsrec_mirror_update_select_dst_port_delvalue(
>>> + ovs_mirror, port_rec);
>>> +                            done = true;
>>> +                            break;
>>> +                        }
>>> +                    }
>>> +                }
>>> +                done = false;
>>> +                for (size_t i = 0; ((i < 
>>> ovs_mirror->n_select_src_port)
>>> +                                                   && (done == 
>>> false)); i++) {
>>> +                    const struct ovsrec_port *port_rec =
>>> + ovs_mirror->select_src_port[i];
>>> +                    for (size_t j = 0; j < port_rec->n_interfaces; 
>>> j++) {
>>> +                        const struct ovsrec_interface *iface_rec;
>>> +
>>> +                        iface_rec = port_rec->interfaces[j];
>>> +                        const char *iface_id =
>>> + smap_get(&iface_rec->external_ids,
>>> + "iface-id");
>>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>>> + ovsrec_mirror_update_select_src_port_delvalue(
>>> + ovs_mirror, port_rec);
>>> +                            done = true;
>>> +                            break;
>>> +                        }
>>> +                    }
>>> +                }
>>> +            } else {
>>> +                /*
>>> +                 * If only 1 source delete the output port
>>> +                 * and then delete the mirror completely
>>> +                 */
>>> +                VLOG_INFO("Only 1 source for the mirror. Hence 
>>> delete it");
>>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>> + ovs_mirror->output_port);
>>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>> + ovs_mirror);
>>> + ovsrec_port_delete(ovs_mirror->output_port);
>>> +                ovsrec_mirror_delete(ovs_mirror);
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    const char *used_node, *used_next;
>>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>>> +    }
>>> +    sset_destroy(&pb_mirrors);
>>> +
>>> +    return true;
>>> +}
>>> +
>>> +static void
>>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>>> +                            struct port_mirror_ctx *pm_ctx,
>>> +                            struct shash *pb_mirror_map)
>>> +{
>>> +    const struct ovsrec_mirror *mirror = NULL;
>>> +
>>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>>> +            const struct ovsrec_port *port_rec = 
>>> mirror->select_dst_port[i];
>>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>> +                const struct ovsrec_interface *iface_rec;
>>> +                iface_rec = port_rec->interfaces[j];
>>> +                const char *logical_port =
>>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>>> +                if (!strcmp(logical_port, pb->logical_port)) {
>>> +                    shash_add_once(pb_mirror_map, mirror->name, 
>>> mirror);
>>> +                }
>>> +            }
>>> +        }
>>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>>> +            const struct ovsrec_port *port_rec = 
>>> mirror->select_src_port[i];
>>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>> +                const struct ovsrec_interface *iface_rec;
>>> +                iface_rec = port_rec->interfaces[j];
>>> +                const char *logical_port =
>>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>>> +                if (!strcmp(logical_port, pb->logical_port)) {
>>> +                    shash_add_once(pb_mirror_map, mirror->name, 
>>> mirror);
>>> +                }
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>> +{
>>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>>> +
>>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
>>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
>>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
>>> +}
>>> +
>>> +
>>> +void
>>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
>>> +{
>>> +    shash_init(ovs_mirrors);
>>> +}
>>> +
>>> +void
>>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>>> +{
>>> +    const struct sbrec_port_binding *pb;
>>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>>> + pm_ctx->port_binding_table) {
>>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>>> +    }
>>> +}
>>> +
>>> +bool
>>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, 
>>> bool removed,
>>> +                     struct port_mirror_ctx *pm_ctx)
>>> +{
>>> +    bool ret = true;
>>> +    struct local_binding *lbinding = local_binding_find(
>>> +                               pm_ctx->local_bindings, 
>>> pb->logical_port);
>>> +
>>> +    if (strcmp(pb->type, "") && (!lbinding)) {
>>> +        return ret;
>>> +    }
>>> +
>>> +    struct shash port_ovs_mirrors = 
>>> SHASH_INITIALIZER(&port_ovs_mirrors);
>>> +
>>> +    /* Need to find if mirror needs update */
>>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>>> +    if (!removed) {
>>> +        if ((pb->n_mirror_rules == 0)
>>> +              && (shash_is_empty(&port_ovs_mirrors))) {
>>> +            /* No mirror update */
>>> +        } else if (pb->n_mirror_rules == 
>>> shash_count(&port_ovs_mirrors)) {
>>> +            /* Though number of mirror rules are same,
>>> +             * need to verify the contents
>>> +             */
>>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
>>> +                if (!shash_find(&port_ovs_mirrors,
>>> + pb->mirror_rules[i]->name)) {
>>> +                    /* Mis match in OVN SB DB and OVS DB
>>> +                     * Delete and Create mirror(s) with proper sources
>>> +                     */
>>> +                    ret = mirror_delete(pb, pm_ctx,
>>> + &port_ovs_mirrors, false);
>>> +                    if (ret) {
>>> +                        ret = mirror_create(pb, pm_ctx);
>>> +                    }
>>> +                    break;
>>> +                }
>>> +            }
>>> +        } else {
>>> +            /* Update Mirror */
>>> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
>>> +                /* create mirror,
>>> +                 * if mirror already exists only update selection
>>> +                 */
>>> +                ret = mirror_create(pb, pm_ctx);
>>> +            } else {
>>> +                /* delete mirror,
>>> +                 * if mirror has other sources only update selection
>>> +                 */
>>> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, 
>>> false);
>>> +            }
>>> +        }
>>> +    } else {
>>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>>> +    }
>>> +
>>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>> + &port_ovs_mirrors) {
>>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>>> +    }
>>> +    shash_destroy(&port_ovs_mirrors);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +bool
>>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>>> +{
>>> +    const struct sbrec_mirror *mirror = NULL;
>>> +    struct ovsrec_mirror *ovs_mirror = NULL;
>>> +
>>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, 
>>> pm_ctx->sb_mirror_table) {
>>> +    /* For each tracked mirror entry check if OVS entry is there*/
>>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, 
>>> mirror->name);
>>> +        if (ovs_mirror) {
>>> +            if (sbrec_mirror_is_deleted(mirror)) {
>>> +                /* Need to delete the mirror in OVS */
>>> +                VLOG_INFO("Delete mirror and remove port");
>>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>> + ovs_mirror->output_port);
>>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>> + ovs_mirror);
>>> + ovsrec_port_delete(ovs_mirror->output_port);
>>> +                ovsrec_mirror_delete(ovs_mirror);
>>> +            } else {
>>> +                mirror_update(mirror, ovs_mirror);
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    return true;
>>> +}
>>> +
>>> +void
>>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>>> +{
>>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>> +                                              ovs_mirrors) {
>>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
>>> +    }
>>> +    shash_destroy(ovs_mirrors);
>>> +}
>>> diff --git a/controller/mirror.h b/controller/mirror.h
>>> new file mode 100644
>>> index 000000000..85b964f55
>>> --- /dev/null
>>> +++ b/controller/mirror.h
>>> @@ -0,0 +1,53 @@
>>> +/* Copyright (c) 2022 Red Hat, Inc.
>>> + *
>>> + * Licensed under the Apache License, Version 2.0 (the "License");
>>> + * you may not use this file except in compliance with the License.
>>> + * You may obtain a copy of the License at:
>>> + *
>>> + *     http://www.apache.org/licenses/LICENSE-2.0
>>> + *
>>> + * Unless required by applicable law or agreed to in writing, software
>>> + * distributed under the License is distributed on an "AS IS" BASIS,
>>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
>>> implied.
>>> + * See the License for the specific language governing permissions and
>>> + * limitations under the License.
>>> + */
>>> +
>>> +#ifndef OVN_MIRROR_H
>>> +#define OVN_MIRROR_H 1
>>> +
>>> +struct ovsdb_idl_txn;
>>> +struct ovsrec_port_table;
>>> +struct ovsrec_bridge;
>>> +struct ovsrec_bridge_table;
>>> +struct ovsrec_open_vswitch_table;
>>> +struct sbrec_chassis;
>>> +struct ovsrec_interface_table;
>>> +struct ovsrec_mirror_table;
>>> +struct sbrec_mirror_table;
>>> +struct sbrec_port_binding_table;
>>> +
>>> +struct port_mirror_ctx {
>>> +    struct shash *ovs_mirrors;
>>> +    struct ovsdb_idl_txn *ovs_idl_txn;
>>> +    const struct ovsrec_port_table *port_table;
>>> +    const struct ovsrec_bridge *br_int;
>>> +    const struct sbrec_chassis *chassis_rec;
>>> +    const struct ovsrec_bridge_table *bridge_table;
>>> +    const struct ovsrec_open_vswitch_table *ovs_table;
>>> +    const struct ovsrec_interface_table *iface_table;
>>> +    const struct ovsrec_mirror_table *mirror_table;
>>> +    const struct sbrec_mirror_table *sb_mirror_table;
>>> +    const struct sbrec_port_binding_table *port_binding_table;
>>> +    struct shash *local_bindings;
>>> +};
>>> +
>>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
>>> +void ovn_port_mirror_init(struct shash *);
>>> +void ovn_port_mirror_destroy(struct shash *);
>>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>>> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
>>> +                                  bool removed,
>>> +                                  struct port_mirror_ctx *pm_ctx);
>>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
>>> +#endif
>>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>>> index 8895c7a2b..15ab17c4a 100644
>>> --- a/controller/ovn-controller.c
>>> +++ b/controller/ovn-controller.c
>>> @@ -78,6 +78,7 @@
>>>   #include "lib/inc-proc-eng.h"
>>>   #include "lib/ovn-l7.h"
>>>   #include "hmapx.h"
>>> +#include "mirror.h"
>>>
>>>   VLOG_DEFINE_THIS_MODULE(main);
>>>
>>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>>>       ovsdb_idl_track_add_column(ovs_idl, 
>>> &ovsrec_port_col_external_ids);
>>> +    mirror_register_ovs_idl(ovs_idl);
>>>   }
>>>
>>>   #define SB_NODES \
>>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>>       SB_NODE(load_balancer, "load_balancer") \
>>>       SB_NODE(fdb, "fdb") \
>>>       SB_NODE(meter, "meter") \
>>> +    SB_NODE(mirror, "mirror") \
>>>       SB_NODE(static_mac_binding, "static_mac_binding")
>>>
>>>   enum sb_engine_node {
>>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>>>       OVS_NODE(bridge, "bridge") \
>>>       OVS_NODE(port, "port") \
>>>       OVS_NODE(interface, "interface") \
>>> -    OVS_NODE(qos, "qos")
>>> +    OVS_NODE(qos, "qos") \
>>> +    OVS_NODE(mirror, "mirror")
>>>
>>>   enum ovs_engine_node {
>>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>>>       free(lbs);
>>>   }
>>>
>>> +/* Mirror Engine */
>>> +struct ed_type_port_mirror {
>>> +    struct shash ovs_mirrors;
>>> +};
>>> +
>>> +static void *
>>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>>> +                    struct engine_arg *arg OVS_UNUSED)
>>> +{
>>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof 
>>> *port_mirror);
>>> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>>> +    return port_mirror;
>>> +}
>>> +
>>> +static void
>>> +en_port_mirror_cleanup(void *data)
>>> +{
>>> +    struct ed_type_port_mirror *port_mirror = data;
>>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>>> +}
>>> +
>>> +static void
>>> +init_port_mirror_ctx(struct engine_node *node,
>>> +                 struct ed_type_runtime_data *rt_data,
>>> +                 struct ed_type_port_mirror *port_mirror_data,
>>> +                 struct port_mirror_ctx *pm_ctx)
>>> +{
>>> +    struct ovsrec_open_vswitch_table *ovs_table =
>>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>>> +            engine_get_input("OVS_open_vswitch", node));
>>> +    struct ovsrec_bridge_table *bridge_table =
>>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>>> +            engine_get_input("OVS_bridge", node));
>>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>>> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, 
>>> ovs_table);
>>> +
>>> +    ovs_assert(br_int && chassis_id);
>>> +    const struct sbrec_chassis *chassis = NULL;
>>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>>> +        engine_ovsdb_node_get_index(
>>> +                engine_get_input("SB_chassis", node),
>>> +                "name");
>>> +
>>> +    if (chassis_id) {
>>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, 
>>> chassis_id);
>>> +    }
>>> +    ovs_assert(chassis);
>>> +
>>> +    struct ovsrec_port_table *port_table =
>>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>>> +            engine_get_input("OVS_port", node));
>>> +
>>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
>>> +        engine_get_input_data("ovs_interface_shadow", node);
>>> +
>>> +    struct ovsrec_mirror_table *mirror_table =
>>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>>> +            engine_get_input("OVS_mirror", node));
>>> +
>>> +    struct sbrec_port_binding_table *pb_table =
>>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>>> +            engine_get_input("SB_port_binding", node));
>>> +
>>> +    struct sbrec_mirror_table *sb_mirror_table =
>>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>>> +            engine_get_input("SB_mirror", node));
>>> +
>>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>> + &port_mirror_data->ovs_mirrors) {
>>> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
>>> +    }
>>> +
>>> +    const struct ovsrec_mirror *ovsmirror = NULL;
>>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>>> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, 
>>> ovsmirror);
>>> +    }
>>> +
>>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>>> +    pm_ctx->port_table = port_table;
>>> +    pm_ctx->iface_table = iface_shadow->iface_table;
>>> +    pm_ctx->mirror_table = mirror_table;
>>> +    pm_ctx->port_binding_table = pb_table;
>>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
>>> +    pm_ctx->br_int = br_int;
>>> +    pm_ctx->chassis_rec = chassis;
>>> +    pm_ctx->bridge_table = bridge_table;
>>> +    pm_ctx->ovs_table = ovs_table;
>>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>>> +}
>>> +
>>> +static void
>>> +en_port_mirror_run(struct engine_node *node, void *data)
>>> +{
>>> +    struct port_mirror_ctx pm_ctx;
>>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>> +
>>> +    ovn_port_mirror_run(&pm_ctx);
>>> +    engine_set_node_state(node, EN_UPDATED);
>>> +}
>>> +
>>> +static bool
>>> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
>>> +{
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    /* There is no tracked data. Fall back to full recompute of 
>>> port_mirror */
>>> +    if (!rt_data->tracked) {
>>> +        return false;
>>> +    }
>>> +
>>> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
>>> +    if (hmap_is_empty(tracked_dp_bindings)) {
>>> +        return true;
>>> +    }
>>> +
>>> +    struct port_mirror_ctx pm_ctx;
>>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>> +
>>> +    struct tracked_datapath *tdp;
>>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>>> +            /* Fall back to full recompute when a local datapath
>>> +             * is added or deleted. */
>>> +            return false;
>>> +        }
>>> +
>>> +        struct shash_node *shash_node;
>>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>>> +            struct tracked_lport *lport = shash_node->data;
>>> +            bool removed =
>>> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? 
>>> true: false;
>>> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, 
>>> &pm_ctx)) {
>>> +                return false;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    engine_set_node_state(node, EN_UPDATED);
>>> +    return true;
>>> +}
>>> +
>>> +static bool
>>> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
>>> +{
>>> +    struct port_mirror_ctx pm_ctx;
>>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>> +
>>> +    /* handle port binding updates (i.,e when the mirror column
>>> +     * of port_binding is updated)
>>> +     */
>>> +    const struct sbrec_port_binding *pb;
>>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>>> + pm_ctx.port_binding_table) {
>>> +        bool removed = sbrec_port_binding_is_deleted(pb);
>>> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
>>> +            return false;
>>> +        }
>>> +    }
>>> +
>>> +    engine_set_node_state(node, EN_UPDATED);
>>> +    return true;
>>> +
>>> +}
>>> +
>>> +static bool
>>> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
>>> +{
>>> +    struct port_mirror_ctx pm_ctx;
>>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>> +    struct ed_type_runtime_data *rt_data =
>>> +        engine_get_input_data("runtime_data", node);
>>> +
>>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>> +
>>> +    /* handle sb mirror updates
>>> +     */
>>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>>> +        return false;
>>> +    }
>>> +
>>> +    engine_set_node_state(node, EN_UPDATED);
>>> +    return true;
>>> +
>>> +}
>>> +
>>>   /* Engine node which is used to handle the Non VIF data like
>>>    *   - OVS patch ports
>>>    *   - Tunnel ports and the related chassis information.
>>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>>>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>>>       ENGINE_NODE(northd_options, "northd_options");
>>>       ENGINE_NODE(dhcp_options, "dhcp_options");
>>> +    ENGINE_NODE(port_mirror, "port_mirror");
>>>
>>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>>>       SB_NODES
>>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>>>       engine_add_input(&en_flow_output, &en_pflow_output,
>>>                        flow_output_pflow_output_handler);
>>>
>>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
>>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>>> +    engine_add_input(&en_port_mirror, &en_ovs_port, 
>>> engine_noop_handler);
>>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>>> +                     engine_noop_handler);
>>> +    engine_add_input(&en_flow_output, &en_port_mirror,
>>> +                     engine_noop_handler);
>>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
>>> +                     port_mirror_runtime_data_handler);
>>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>>> +                     port_mirror_sb_mirror_handler);
>>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>>> +                     port_mirror_port_binding_handler);
>>> +
>>>       struct engine_arg engine_arg = {
>>>           .sb_idl = ovnsb_idl_loop.idl,
>>>           .ovs_idl = ovs_idl_loop.idl,
>>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>>>
>>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>>>                                       time_msec());
>>> -                    if (ovnsb_idl_txn) {
>>> -                        if (ofctrl_has_backlog()) {
>>> -                            /* When there are in-flight messages 
>>> pending to
>>> -                             * ovs-vswitchd, we should hold on 
>>> recomputing so
>>> -                             * that the previous flow installations 
>>> won't be
>>> -                             * delayed.  However, we still want to 
>>> try if
>>> -                             * recompute is not needed and we can 
>>> quickly
>>> -                             * incrementally process the new 
>>> changes, to avoid
>>> -                             * unnecessarily forced recomputes 
>>> later on.  This
>>> -                             * is because the OVSDB change tracker 
>>> cannot
>>> -                             * preserve tracked changes across 
>>> iterations.  If
>>> -                             * change tracking is improved, we can 
>>> simply skip
>>> -                             * this round of engine_run and 
>>> continue processing
>>> -                             * acculated changes incrementally 
>>> later when
>>> -                             * ofctrl_has_backlog() returns false. */
>>> -                            engine_run(false);
>>> -                        } else {
>>> -                            engine_run(true);
>>> -                        }
>>> -                    } else {
>>> -                        /* Even if there's no SB DB transaction 
>>> available,
>>> +
>>> +                    bool allow_engine_recompute = true;
>>> +
>>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>>> + ofctrl_has_backlog()) {
>>> +                        /* When there are in-flight messages 
>>> pending to
>>> +                         * ovs-vswitchd, we should hold on 
>>> recomputing so
>>> +                         * that the previous flow installations 
>>> won't be
>>> +                         * delayed.  However, we still want to try if
>>> +                         * recompute is not needed and we can quickly
>>> +                         * incrementally process the new changes, 
>>> to avoid
>>> +                         * unnecessarily forced recomputes later 
>>> on.  This
>>> +                         * is because the OVSDB change tracker cannot
>>> +                         * preserve tracked changes across 
>>> iterations.  If
>>> +                         * change tracking is improved, we can 
>>> simply skip
>>> +                         * this round of engine_run and continue 
>>> processing
>>> +                         * acculated changes incrementally later when
>>> +                         * ofctrl_has_backlog() returns false. */
>>> +
>>> +                        /* Even if there's no SB/OVS DB transaction 
>>> available,
>>>                            * try to run the engine so that we can 
>>> handle any
>>>                            * incremental changes that don't require 
>>> a recompute.
>>>                            * If a recompute is required, the engine 
>>> will abort,
>>>                            * triggerring a full run in the next 
>>> iteration.
>>>                            */
>>> -                        engine_run(false);
>>> +                        allow_engine_recompute = false;
>>>                       }
>>> +
>>> +                    engine_run(allow_engine_recompute);
>>> +
>>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>>>                                      time_msec());
>>>                       if (engine_has_updated()) {
>>> diff --git a/northd/en-northd.c b/northd/en-northd.c
>>> index 7fe83db64..608220b1f 100644
>>> --- a/northd/en-northd.c
>>> +++ b/northd/en-northd.c
>>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void 
>>> *data)
>>>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
>>>       input_data.nbrec_static_mac_binding_table =
>>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>>> +    input_data.nbrec_mirror_table =
>>> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>>>
>>>       input_data.sbrec_sb_global_table =
>>>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, 
>>> void *data)
>>>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>>>       input_data.sbrec_static_mac_binding_table =
>>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>>> +    input_data.sbrec_mirror_table =
>>> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>>>
>>>       northd_run(&input_data, data,
>>>                  eng_ctx->ovnnb_idl_txn,
>>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>>> index 54e0ad3b0..ac27a730e 100644
>>> --- a/northd/inc-proc-northd.c
>>> +++ b/northd/inc-proc-northd.c
>>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>>       NB_NODE(acl, "acl") \
>>>       NB_NODE(logical_router, "logical_router") \
>>>       NB_NODE(qos, "qos") \
>>> +    NB_NODE(mirror, "mirror") \
>>>       NB_NODE(meter, "meter") \
>>>       NB_NODE(meter_band, "meter_band") \
>>>       NB_NODE(logical_router_port, "logical_router_port") \
>>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>>       SB_NODE(logical_flow, "logical_flow") \
>>>       SB_NODE(logical_dp_group, "logical_DP_group") \
>>>       SB_NODE(multicast_group, "multicast_group") \
>>> +    SB_NODE(mirror, "mirror") \
>>>       SB_NODE(meter, "meter") \
>>>       SB_NODE(meter_band, "meter_band") \
>>>       SB_NODE(datapath_binding, "datapath_binding") \
>>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop 
>>> *nb,
>>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
>>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
>>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
>>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>>>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
>>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop 
>>> *nb,
>>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
>>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
>>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
>>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index b7388afc5..52abdda28 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>>>       free(requested_chassis_sb);
>>>   }
>>>
>>> +static void
>>> +do_sb_mirror_addition(struct northd_input *input_data,
>>> +                      const struct ovn_port *op)
>>> +{
>>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>> +        const struct sbrec_mirror *sb_mirror;
>>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>>> + input_data->sbrec_mirror_table) {
>>> +            if (!strcmp(sb_mirror->name,
>>> + op->nbsp->mirror_rules[i]->name)) {
>>> +                /* Add the value to SB */
>>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>>> + sb_mirror);
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +static void
>>> +sbrec_port_binding_update_mirror_rules(struct northd_input 
>>> *input_data,
>>> +                                       const struct ovn_port *op)
>>> +{
>>> +    size_t i = 0;
>>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>>> +        /* Needs deletion in SB */
>>> +        struct shash nb_mirror_rules = 
>>> SHASH_INITIALIZER(&nb_mirror_rules);
>>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>> +            shash_add(&nb_mirror_rules,
>>> + op->nbsp->mirror_rules[i]->name,
>>> + op->nbsp->mirror_rules[i]);
>>> +        }
>>> +
>>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>> +            if (!shash_find(&nb_mirror_rules,
>>> + op->sb->mirror_rules[i]->name)) {
>>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>> + op->sb->mirror_rules[i]);
>>> +            }
>>> +        }
>>> +
>>> +        struct shash_node *node, *next;
>>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>> +            shash_delete(&nb_mirror_rules, node);
>>> +        }
>>> +        shash_destroy(&nb_mirror_rules);
>>> +
>>> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
>>> +        /* Needs addition in SB */
>>> +        do_sb_mirror_addition(input_data, op);
>>> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
>>> +        /*
>>> +         * Check if its the same mirrors on both SB and NB DBs
>>> +         * If not update accordingly.
>>> +         */
>>> +        bool needs_sb_addition = false;
>>> +        struct shash nb_mirror_rules = 
>>> SHASH_INITIALIZER(&nb_mirror_rules);
>>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>> +            shash_add(&nb_mirror_rules,
>>> + op->nbsp->mirror_rules[i]->name,
>>> + op->nbsp->mirror_rules[i]);
>>> +        }
>>> +
>>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>> +            if (!shash_find(&nb_mirror_rules,
>>> + op->sb->mirror_rules[i]->name)) {
>>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>> + op->sb->mirror_rules[i]);
>>> +                    needs_sb_addition = true;
>>> +            }
>>> +        }
>>> +
>>> +        struct shash_node *node, *next;
>>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>> +            shash_delete(&nb_mirror_rules, node);
>>> +        }
>>> +        shash_destroy(&nb_mirror_rules);
>>> +
>>> +        if (needs_sb_addition) {
>>> +            do_sb_mirror_addition(input_data, op);
>>> +        }
>>> +    }
>>> +}
>>> +
>>>   static void
>>>   ovn_port_update_sbrec(struct northd_input *input_data,
>>>                         struct ovsdb_idl_txn *ovnsb_txn,
>>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input 
>>> *input_data,
>>>           }
>>>           sbrec_port_binding_set_external_ids(op->sb, &ids);
>>>           smap_destroy(&ids);
>>> +
>>> +        if (!op->nbsp->n_mirror_rules) {
>>> +            /* Nothing is set. Clear mirror_rules from pb. */
>>> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>>> +        } else {
>>> +            /* Check if SB DB update needed */
>>> + sbrec_port_binding_update_mirror_rules(input_data, op);
>>> +        }
>>> +
>>>       }
>>>       if (op->tunnel_key != op->sb->tunnel_key) {
>>>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>>>       shash_destroy(&sb_meters);
>>>   }
>>>
>>> +static bool
>>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>>> +                  const struct sbrec_mirror *sb_mirror)
>>> +{
>>> +
>>> +    if (nb_mirror->index != sb_mirror->index) {
>>> +        return true;
>>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>>> +        return true;
>>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>>> +        return true;
>>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>>> +        return true;
>>> +    }
>>> +
>>> +    return false;
>>> +}
>>> +
>>> +static void
>>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>>> +                             const char *mirror_name,
>>> +                             const struct nbrec_mirror *nb_mirror,
>>> +                             struct shash *sb_mirrors,
>>> +                             struct sset *used_sb_mirrors)
>>> +{
>>> +    const struct sbrec_mirror *sb_mirror;
>>> +    bool new_sb_mirror = false;
>>> +
>>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>>> +    if (!sb_mirror) {
>>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>>> +        new_sb_mirror = true;
>>> +    }
>>> +    sset_add(used_sb_mirrors, mirror_name);
>>> +
>>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, 
>>> sb_mirror)) {
>>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>>> +    }
>>> +}
>>> +
>>> +static void
>>> +sync_mirrors(struct northd_input *input_data,
>>> +            struct ovsdb_idl_txn *ovnsb_txn)
>>> +{
>>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>>> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
>>> +
>>> +    const struct sbrec_mirror *sb_mirror;
>>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, 
>>> input_data->sbrec_mirror_table) {
>>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>>> +    }
>>> +
>>> +    const struct nbrec_mirror *nb_mirror;
>>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, 
>>> input_data->nbrec_mirror_table) {
>>> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, 
>>> nb_mirror,
>>> +                                     &sb_mirrors, &used_sb_mirrors);
>>> +    }
>>> +
>>> +    const char *used_mirror;
>>> +    const char *used_mirror_next;
>>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, 
>>> &used_sb_mirrors) {
>>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
>>> +        sset_delete(&used_sb_mirrors, 
>>> SSET_NODE_FROM_NAME(used_mirror));
>>> +    }
>>> +    sset_destroy(&used_sb_mirrors);
>>> +
>>> +    struct shash_node *node, *next;
>>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>>> +        sbrec_mirror_delete(node->data);
>>> +        shash_delete(&sb_mirrors, node);
>>> +    }
>>> +    shash_destroy(&sb_mirrors);
>>> +}
>>> +
>>>   /*
>>>    * struct 'dns_info' is used to sync the DNS records between OVN 
>>> Northbound db
>>>    * and Southbound db.
>>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>>> +    sync_mirrors(input_data, ovnsb_txn);
>>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
>>>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>>> diff --git a/northd/northd.h b/northd/northd.h
>>> index da90e2815..17a62ea10 100644
>>> --- a/northd/northd.h
>>> +++ b/northd/northd.h
>>> @@ -36,6 +36,7 @@ struct northd_input {
>>>       const struct nbrec_acl_table *nbrec_acl_table;
>>>       const struct nbrec_static_mac_binding_table
>>>           *nbrec_static_mac_binding_table;
>>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>>>
>>>       /* Southbound table references */
>>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
>>> @@ -55,6 +56,7 @@ struct northd_input {
>>>       const struct sbrec_chassis_private_table 
>>> *sbrec_chassis_private_table;
>>>       const struct sbrec_static_mac_binding_table
>>>           *sbrec_static_mac_binding_table;
>>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>>>
>>>       /* Indexes */
>>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
>>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>>> index 174364c8b..01de55222 100644
>>> --- a/ovn-nb.ovsschema
>>> +++ b/ovn-nb.ovsschema
>>> @@ -1,7 +1,7 @@
>>>   {
>>>       "name": "OVN_Northbound",
>>> -    "version": "6.3.0",
>>> -    "cksum": "4042813038 31869",
>>> +    "version": "6.4.0",
>>> +    "cksum": "589874483 33352",
>>>       "tables": {
>>>           "NB_Global": {
>>>               "columns": {
>>> @@ -132,6 +132,11 @@
>>>                                               "refType": "weak"},
>>>                                    "min": 0,
>>>                                    "max": 1}},
>>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>>> +                                          "refTable": "Mirror",
>>> +                                          "refType": "weak"},
>>> +                                  "min": 0,
>>> +                                  "max": "unlimited"}},
>>>                   "ha_chassis_group": {
>>>                       "type": {"key": {"type": "uuid",
>>>                                        "refTable": "HA_Chassis_Group",
>>> @@ -301,6 +306,28 @@
>>>                       "type": {"key": "string", "value": "string",
>>>                                "min": 0, "max": "unlimited"}}},
>>>               "isRoot": false},
>>> +        "Mirror": {
>>> +            "columns": {
>>> +                "name": {"type": "string"},
>>> +                "filter": {"type": {"key": {"type": "string",
>>> +                                            "enum": ["set", 
>>> ["from-lport",
>>> + "to-lport",
>>> + "both"]]}}},
>>> +                "sink":{"type": "string"},
>>> +                "type": {"type": {"key": {"type": "string",
>>> +                                            "enum": ["set", ["gre",
>>> + "erspan"]]}}},
>>> +                "index": {"type": "integer"},
>>> +                "src": {"type": {"key": {"type": "uuid",
>>> +                                           "refTable": 
>>> "Logical_Switch_Port",
>>> +                                           "refType": "weak"},
>>> +                                   "min": 0,
>>> +                                   "max": "unlimited"}},
>>> +                "external_ids": {
>>> +                    "type": {"key": "string", "value": "string",
>>> +                             "min": 0, "max": "unlimited"}}},
>>> +            "indexes": [["name"]],
>>> +            "isRoot": true},
>>>           "Meter": {
>>>               "columns": {
>>>                   "name": {"type": "string"},
>>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>>> index f41e9d7c0..d8730c8fc 100644
>>> --- a/ovn-nb.xml
>>> +++ b/ovn-nb.xml
>>> @@ -1554,6 +1554,11 @@
>>>         </column>
>>>       </group>
>>>
>>> +    <column name="mirror_rules">
>>> +        Mirror rules that apply to logical switch port which is the 
>>> source.
>>> +        Please see the <ref table="Mirror"/> table.
>>> +    </column>
>>> +
>>>       <column name="ha_chassis_group">
>>>         References a row in the OVN Northbound database's
>>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>>> @@ -2491,6 +2496,64 @@
>>>       </column>
>>>     </table>
>>>
>>> +  <table name="Mirror" title="Mirror Entry">
>>> +    <p>
>>> +      Each row in this table represents one Mirror that can be used 
>>> for
>>> +      port mirroring. These Mirrors are referenced by the
>>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/> 
>>> column in
>>> +      the <ref table="Logical_Switch_Port"/> table.
>>> +    </p>
>>> +
>>> +    <column name="name">
>>> +      <p>
>>> +        Represents the name of the mirror.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="filter">
>>> +      <p>
>>> +        The value of this field represents selection criteria of 
>>> the mirror.
>>> +        Supported values for filter to-lport / from-lport / both
>>> +        to-lport - to mirror packets coming into logical port
>>> +        from-lport - to mirror packets going out of logical port
>>> +        both - to mirror packets coming into and going out of 
>>> logical port.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="sink">
>>> +      <p>
>>> +        The value of this field represents the destination/sink of 
>>> the mirror.
>>> +        The value it takes is an IP address of the sink port.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="type">
>>> +      <p>
>>> +        The value of this field represents the type of the tunnel 
>>> used for
>>> +        sending the mirrored packets. Supported Tunnel types gre 
>>> and erspan
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="index">
>>> +      <p>
>>> +        The value of this field represents the tunnel ID. Depending 
>>> on the
>>> +        tunnel type configured, GRE key value if type GRE and 
>>> erspan_idx value
>>> +        if ERSPAN
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="src">
>>> +      <p>
>>> +        The value of this field represents a list of source ports 
>>> for the
>>> +        mirror. Please see the <ref table="Logical_Switch_Port"/> 
>>> table.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="external_ids">
>>> +      See <em>External IDs</em> at the beginning of this document.
>>> +    </column>
>>> +  </table>
>>> +
>>>     <table name="Meter" title="Meter entry">
>>>       <p>
>>>         Each row in this table represents a meter that can be used 
>>> for QoS or
>>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>>> index 576ebbdeb..b83134416 100644
>>> --- a/ovn-sb.ovsschema
>>> +++ b/ovn-sb.ovsschema
>>> @@ -1,7 +1,7 @@
>>>   {
>>>       "name": "OVN_Southbound",
>>> -    "version": "20.25.0",
>>> -    "cksum": "53184112 28845",
>>> +    "version": "20.26.0",
>>> +    "cksum": "2344151793 30004",
>>>       "tables": {
>>>           "SB_Global": {
>>>               "columns": {
>>> @@ -142,6 +142,23 @@
>>>               "indexes": [["datapath", "tunnel_key"],
>>>                           ["datapath", "name"]],
>>>               "isRoot": true},
>>> +        "Mirror": {
>>> +            "columns": {
>>> +                "name": {"type": "string"},
>>> +                "filter": {"type": {"key": {"type": "string",
>>> +                                            "enum": ["set",
>>> + ["from-lport",
>>> + "to-lport","both"]]}}},
>>> +                "sink":{"type": "string"},
>>> +                "type": {"type": {"key": {"type": "string",
>>> +                                            "enum": ["set",
>>> +                                                     ["gre", 
>>> "erspan"]]}}},
>>> +                "index": {"type": "integer"},
>>> +                "external_ids": {
>>> +                    "type": {"key": "string", "value": "string",
>>> +                             "min": 0, "max": "unlimited"}}},
>>> +            "indexes": [["name"]],
>>> +            "isRoot": true},
>>>           "Meter": {
>>>               "columns": {
>>>                   "name": {"type": "string"},
>>> @@ -230,6 +247,11 @@
>>> "refTable": "Encap",
>>> "refType": "weak"},
>>>                                       "min": 0, "max": "unlimited"}},
>>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>>> +                                          "refTable": "Mirror",
>>> +                                          "refType": "weak"},
>>> +                                  "min": 0,
>>> +                                  "max": "unlimited"}},
>>>                   "mac": {"type": {"key": "string",
>>>                                    "min": 0,
>>>                                    "max": "unlimited"}},
>>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>>> index 315d60853..05c0db6b4 100644
>>> --- a/ovn-sb.xml
>>> +++ b/ovn-sb.xml
>>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>>>       </column>
>>>     </table>
>>>
>>> +  <table name="Mirror" title="Mirror Entry">
>>> +    <p>
>>> +      Each row in this table represents one Mirror that can be used 
>>> for
>>> +      port mirroring. These Mirrors are referenced by the
>>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
>>> +      the <ref table="Port_Binding"/> table.
>>> +    </p>
>>> +
>>> +    <column name="name">
>>> +      <p>
>>> +        Represents the name of the mirror.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="filter">
>>> +      <p>
>>> +        The value of this field represents selection criteria of 
>>> the mirror.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="sink">
>>> +      <p>
>>> +        The value of this field represents the destination/sink of 
>>> the mirror.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="type">
>>> +      <p>
>>> +        The value of this field represents the type of the tunnel 
>>> used for
>>> +        sending the mirrored packets
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="index">
>>> +      <p>
>>> +        The value of this field represents the key/idx depending on 
>>> the
>>> +        tunnel type configured
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="external_ids">
>>> +      See <em>External IDs</em> at the beginning of this document.
>>> +    </column>
>>> +  </table>
>>> +
>>>     <table name="Meter" title="Meter entry">
>>>       <p>
>>>         Each row in this table represents a meter that can be used 
>>> for QoS or
>>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>>>         </column>
>>>       </group>
>>>
>>> +    <column name="mirror_rules">
>>> +        Mirror rules that apply to the port binding.
>>> +        Please see the <ref table="Mirror"/> table.
>>> +    </column>
>>> +
>>>       <group title="Patch Options">
>>>         <p>
>>>           These options apply to logical ports with <ref 
>>> column="type"/> of
>>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
>>> index 4d480e357..d79f9d929 100644
>>> --- a/tests/ovn-nbctl.at
>>> +++ b/tests/ovn-nbctl.at
>>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>>>
>>>   dnl 
>>> ---------------------------------------------------------------------
>>>
>>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
>>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
>>> +AT_CHECK([ovn-nbctl ls-add sw0])
>>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>>> +
>>> +dnl Add duplicate mirror name
>>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 
>>> 10.10.10.5], [1], [], [stderr])
>>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>>> +
>>> +dnl Attach invalid source port to mirror
>>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], 
>>> [stderr])
>>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>>> +
>>> +dnl Attach source port to invalid mirror
>>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], 
>>> [stderr])
>>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
>>> +
>>> +dnl Attach source port to mirror
>>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>>> +
>>> +dnl Attach one more source port to mirror
>>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>>> +
>>> +dnl Verify if multiple ports are attached to the same mirror properly
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  gre
>>> +  Sink     :  10.10.10.1
>>> +  Filter   :  from-lport
>>> +  Index/Key:  0
>>> +  Sources  :  None attached
>>> +mirror2:
>>> +  Type     :  erspan
>>> +  Sink     :  10.10.10.2
>>> +  Filter   :  both
>>> +  Index/Key:  1
>>> +  Sources  :  None attached
>>> +mirror3:
>>> +  Type     :  gre
>>> +  Sink     :  10.10.10.3
>>> +  Filter   :  to-lport
>>> +  Index/Key:  2
>>> +  Sources  :  sw0-port1  sw0-port3
>>> +])
>>> +
>>> +dnl Detach one source port from mirror
>>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>>> +
>>> +dnl Verify if detach source port from mirror happens properly
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  gre
>>> +  Sink     :  10.10.10.1
>>> +  Filter   :  from-lport
>>> +  Index/Key:  0
>>> +  Sources  :  None attached
>>> +mirror2:
>>> +  Type     :  erspan
>>> +  Sink     :  10.10.10.2
>>> +  Filter   :  both
>>> +  Index/Key:  1
>>> +  Sources  :  None attached
>>> +mirror3:
>>> +  Type     :  gre
>>> +  Sink     :  10.10.10.3
>>> +  Filter   :  to-lport
>>> +  Index/Key:  2
>>> +  Sources  :  sw0-port1
>>> +])
>>> +
>>> +dnl Delete a single mirror which has source attached.
>>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
>>> +
>>> +dnl Check if the detach happened from source properly
>>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules 
>>> |  cut -b 3], [0], [dnl
>>> +
>>> +])
>>> +
>>> +dnl Check if the mirror deleted properly
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  gre
>>> +  Sink     :  10.10.10.1
>>> +  Filter   :  from-lport
>>> +  Index/Key:  0
>>> +  Sources  :  None attached
>>> +mirror2:
>>> +  Type     :  erspan
>>> +  Sink     :  10.10.10.2
>>> +  Filter   :  both
>>> +  Index/Key:  1
>>> +  Sources  :  None attached
>>> +])
>>> +
>>> +dnl Delete another mirror
>>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
>>> +
>>> +dnl Update the Sink address
>>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>>> +
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  gre
>>> +  Sink     :  192.168.1.13
>>> +  Filter   :  from-lport
>>> +  Index/Key:  0
>>> +  Sources  :  None attached
>>> +])
>>> +
>>> +dnl Delete all mirrors
>>> +AT_CHECK([ovn-nbctl mirror-del])
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +])])
>>> +
>>> +dnl 
>>> ---------------------------------------------------------------------
>>> +
>>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>>>   AT_CHECK([ovn-nbctl lr-add lr0])
>>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], 
>>> [1], [],
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index 4f399eccb..4e6c268e4 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} 
>>> meter_me__${acl2}
>>>   AT_CLEANUP
>>>   ])
>>>
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([Check NB-SB mirrors sync])
>>> +AT_KEYWORDS([mirrors])
>>> +ovn_start
>>> +
>>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>> +"10.10.10.2"
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>> +erspan
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>> +0
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>> +both
>>> +])
>>> +
>>> +check ovn-nbctl --wait=sb \
>>> +    -- set mirror . sink=192.168.1.13
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>> +"192.168.1.13"
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>> +erspan
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>> +0
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>> +both
>>> +])
>>> +
>>> +check ovn-nbctl --wait=sb \
>>> +    -- set mirror . type=gre
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>> +gre
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>> +"192.168.1.13"
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>> +0
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>> +both
>>> +])
>>> +
>>> +check ovn-nbctl --wait=sb \
>>> +    -- set mirror . index=12
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>> +12
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>> +gre
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>> +"192.168.1.13"
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>> +both
>>> +])
>>> +
>>> +check ovn-nbctl --wait=sb \
>>> +    -- set mirror . filter=to-lport
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>> +to-lport
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>> +12
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>> +gre
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>> +"192.168.1.13"
>>> +])
>>> +
>>> +AT_CLEANUP
>>> +])
>>> +
>>>   OVN_FOR_EACH_NORTHD_NO_HV([
>>>   AT_SETUP([ACL skip hints for stateless config])
>>>   AT_KEYWORDS([acl])
>>> diff --git a/tests/ovn.at b/tests/ovn.at
>>> index f8b8db4df..cd5527ea1 100644
>>> --- a/tests/ovn.at
>>> +++ b/tests/ovn.at
>>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>>>   AT_CLEANUP
>>>   ])
>>>
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror])
>>> +AT_KEYWORDS([Mirror])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
>>> +    ofport-request=1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
>>> +    ofport-request=1
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +ovn-nbctl dump-flows > sbflows
>>> +AT_CAPTURE_FILE([sbflows])
>>> +
>>> +for i in 1 2; do
>>> +    : > vif$i.expected
>>> +done
>>> +
>>> +net_add n2
>>> +
>>> +sim_add hv2
>>> +as hv2
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:02:02:00\"
>>> +ovn_attach n2 br-phys 192.168.1.12
>>> +
>>> +OVN_POPULATE_ARP
>>> +
>>> +as hv1
>>> +
>>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST 
>>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
>>> +#
>>> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
>>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
>>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
>>> +# provided, then it should be the ip and icmp checksums of the packet
>>> +# responded; otherwise, no reply is expected.
>>> +# In the absence of an ip checksum calculation helpers, this relies
>>> +# on the caller to provide the checksums for the ip and icmp headers.
>>> +# XXX This should be more systematic.
>>> +#
>>> +# INPORT is an lport number, e.g. 11 for vif11.
>>> +# ETH_SRC and ETH_DST are each 12 hex digits.
>>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>>> +# ENCAP_TYPE - gre or erspan
>>> +# FILTER - Mirror Filter - to-lport / from-lport
>>> +test_ipv4_icmp_request() {
>>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 
>>> ip_chksum=$6 icmp_chksum=$7
>>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 
>>> mirror_encap_type=${10} mirror_filter=${11}
>>> +    shift; shift; shift; shift; shift; shift; shift
>>> +    shift; shift; shift; shift;
>>> +
>>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>>> +    local ip_ttl=02
>>> +    local icmp_id=5fbf
>>> +    local icmp_seq=0001
>>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>>> +    local icmp_type_code_request=0800
>>> +    local 
>>> icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>> +    local 
>>> packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>>> +
>>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
>>> +
>>> +    # Expect to receive the reply, if any. In same port where 
>>> packet was sent.
>>> +    # Note: src and dst fields are expected to be reversed.
>>> +    local icmp_type_code_response=0000
>>> +    local reply_icmp_ttl=fe
>>> +    local 
>>> reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>> +    local 
>>> reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>>> +    echo $reply >> vif$inport.expected
>>> +    local remote_mac=000000020200
>>> +    local local_mac=000000010200
>>> +    if test ${mirror_encap_type} = "gre" ; then
>>> +        local 
>>> ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>>> +        if test ${mirror_filter} = "to-lport" ; then
>>> +            local 
>>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>>> +        elif test ${mirror_filter} = "from-lport" ; then
>>> +            local 
>>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>>> +        fi
>>> +    elif test ${mirror_encap_type} = "erspan" ; then
>>> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>>> +        local erspan_seq0=100088be000000001000000000000000
>>> +        local erspan_seq1=100088be000000011000000000000000
>>> +        if test ${mirror_filter} = "to-lport" ; then
>>> +            local 
>>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply} 
>>>
>>> +        elif test ${mirror_filter} = "from-lport" ; then
>>> +            local 
>>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>>> +        fi
>>> +    fi
>>> +    echo $mirror >> br-phys_n1.expected
>>> +
>>> +}
>>> +
>>> +# Set IPs
>>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>>> +l1_ip=$(ip_to_hex 192 168 1 2)
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +
>>> +# Send ping packet and check for mirrored packet of the reply
>>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip 
>>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>>> +
>>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], 
>>> [br-phys_n1.expected])
>>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>> +
>>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>> +rm -f br-phys_n1.expected
>>> +rm -f vif1.expected
>>> +
>>> +check ovn-nbctl set mirror . type=erspan
>>> +
>>> +# Send ping packet and check for mirrored packet of the reply
>>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip 
>>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>>> +
>>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], 
>>> [br-phys_n1.expected])
>>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>> +
>>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>> +rm -f br-phys_n1.expected
>>> +rm -f vif1.expected
>>> +
>>> +check ovn-nbctl set mirror . filter=from-lport
>>> +
>>> +# Send ping packet and check for mirrored packet of the request
>>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip 
>>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>>> +
>>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], 
>>> [br-phys_n1.expected])
>>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>> +
>>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>> +rm -f br-phys_n1.expected
>>> +rm -f vif1.expected
>>> +
>>> +check ovn-nbctl set mirror . type=gre
>>> +
>>> +# Send ping packet and check for mirrored packet of the request
>>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip 
>>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>>> +
>>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], 
>>> [br-phys_n1.expected])
>>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>> +
>>> +echo "---------OVN NB Mirror-----"
>>> +ovn-nbctl mirror-list
>>> +
>>> +echo "---------OVS Mirror----"
>>> +ovs-vsctl list Mirror
>>> +
>>> +echo "-----------------------"
>>> +
>>> +echo "Verifying Mirror deletion in OVS"
>>> +# Set vif1 iface-id such that OVN releases port binding
>>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>>> +])
>>> +
>>> +# Set vif1 iface-id back to ls1-lp1
>>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = 
>>> "mirror0"])
>>> +
>>> +# Delete vif1 so that OVN releases port binding
>>> +check ovs-vsctl del-port br-int vif1
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>>> +
>>> +OVN_CLEANUP([hv1], [hv2])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror test bulk swap attachments])
>>> +AT_KEYWORDS([Mirror test bulk swap attachments])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls1-lp2 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +# Create logical port ls2-lp2 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif3 -- \
>>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>> +
>>> +ovs-vsctl -- add-port br-int vif4 -- \
>>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>> +
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +# Equal detaches and attaches
>>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>> +
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>> +
>>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror test bulk attach multiple])
>>> +AT_KEYWORDS([Mirror test bulk attach multiple])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls1-lp2 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +# Create logical port ls2-lp2 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif3 -- \
>>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>> +
>>> +ovs-vsctl -- add-port br-int vif4 -- \
>>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>> +
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +check ovn-nbctl mirror-del
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +
>>> +# Attaches multiple mirrors
>>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>>> +
>>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror test bulk more detach and less attach])
>>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls1-lp2 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +# Create logical port ls2-lp2 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif3 -- \
>>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>> +
>>> +ovs-vsctl -- add-port br-int vif4 -- \
>>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl --wait=hv sync
>>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl --wait=hv sync
>>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +check ovn-nbctl mirror-del
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +# Detaches more than attaches
>>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
>>> +
>>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror test bulk attach more than detach])
>>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls1-lp2 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +# Create logical port ls2-lp2 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif3 -- \
>>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>> +
>>> +ovs-vsctl -- add-port br-int vif4 -- \
>>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>> +
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +# Attaches more than detaches
>>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +as hv1
>>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>> +
>>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>> +
>>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>> +
>>> +OVN_FOR_EACH_NORTHD([
>>> +AT_SETUP([Mirror test bulk detach multiple])
>>> +AT_KEYWORDS([Mirror test bulk detach multiple])
>>> +ovn_start
>>> +
>>> +# Logical network:
>>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>>> +
>>> +ovn-nbctl lr-add R1
>>> +
>>> +ovn-nbctl ls-add ls1
>>> +ovn-nbctl ls-add ls2
>>> +
>>> +# Connect ls1 to R1
>>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>> +    type=router options:router-port=ls1 
>>> addresses=\"00:00:00:01:02:f1\"
>>> +
>>> +# Connect ls2 to R1
>>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>> +    type=router options:router-port=ls2 
>>> addresses=\"00:00:00:01:02:f2\"
>>> +
>>> +# Create logical port ls1-lp1 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>> +
>>> +# Create logical port ls1-lp2 in ls1
>>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>> +
>>> +# Create logical port ls2-lp1 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>> +
>>> +# Create logical port ls2-lp2 in ls2
>>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>> +
>>> +ovn-nbctl lsp-add ls1 ln-public
>>> +ovn-nbctl lsp-set-type ln-public localnet
>>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>> +
>>> +# Create one hypervisor and create OVS ports corresponding to 
>>> logical ports.
>>> +net_add n1
>>> +
>>> +sim_add hv1
>>> +as hv1
>>> +ovs-vsctl add-br br-phys -- set bridge br-phys 
>>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>> +ovn_attach n1 br-phys 192.168.1.11
>>> +
>>> +ovs-vsctl -- add-port br-int vif1 -- \
>>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif2 -- \
>>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>> +
>>> +ovs-vsctl -- add-port br-int vif3 -- \
>>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>> +
>>> +ovs-vsctl -- add-port br-int vif4 -- \
>>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>> +
>>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>> +
>>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>>> +wait_for_ports_up
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>> +
>>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>> +
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +# Detaches all
>>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>> +check ovn-nbctl --wait=hv sync
>>> +
>>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>>> +
>>> +OVN_CLEANUP([hv1])
>>> +AT_CLEANUP
>>> +])
>>>
>>>   OVN_FOR_EACH_NORTHD([
>>>   AT_SETUP([Port Groups])
>>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>>> index 811468dc6..af2e61435 100644
>>> --- a/utilities/ovn-nbctl.c
>>> +++ b/utilities/ovn-nbctl.c
>>> @@ -271,6 +271,19 @@ QoS commands:\n\
>>>                               remove QoS rules from SWITCH\n\
>>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>>>   \n\
>>> +Mirror commands:\n\
>>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
>>> +                            add a mirror with given name\n\
>>> +                            specify TYPE 'gre' or 'erspan'\n\
>>> +                            specify the tunnel INDEX value\n\
>>> +                                (indicates key if GRE\n\
>>> +                                 erpsan_idx if ERSPAN)\n\
>>> +                            specify FILTER for mirroring selection\n\
>>> +                                'to-lport' / 'from-lport' / 'both'\n\
>>> +                            specify Sink / Destination i.e. Remote 
>>> IP\n\
>>> +  mirror-del [NAME]         remove mirrors\n\
>>> +  mirror-list               print mirrors\n\
>>> +\n\
>>>   Meter commands:\n\
>>>     [--fair]\n\
>>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
>>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>>>                               set dhcpv6 options for PORT\n\
>>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>>>     lsp-get-ls PORT           get the logical switch which the port 
>>> belongs to\n\
>>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
>>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>>>   \n\
>>>   Forwarding group commands:\n\
>>>     [--liveness]\n\
>>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>>>       ovsdb_idl_add_column(ctx->idl, 
>>> &nbrec_logical_switch_port_col_type);
>>>   }
>>>
>>> +static void
>>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, 
>>> &nbrec_logical_switch_port_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl,
>>> + &nbrec_logical_switch_port_col_mirror_rules);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>> +}
>>> +
>>> +static int
>>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
>>> +{
>>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>>> +
>>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
>>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
>>> +
>>> +    return strcmp(mirror1->name,mirror2->name);
>>> +}
>>> +
>>> +static char * OVS_WARN_UNUSED_RESULT
>>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>>> +                    bool must_exist,
>>> +                    const struct nbrec_mirror **mirror_p)
>>> +{
>>> +    const struct nbrec_mirror *mirror = NULL;
>>> +    *mirror_p = NULL;
>>> +
>>> +    struct uuid mirror_uuid;
>>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>>> +    if (is_uuid) {
>>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
>>> +    }
>>> +
>>> +    if (!mirror) {
>>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>> +            if (!strcmp(mirror->name, id)) {
>>> +                break;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    if (!mirror && must_exist) {
>>> +        return xasprintf("%s: mirror %s not found",
>>> +                         id, is_uuid ? "UUID" : "name");
>>> +    }
>>> +
>>> +    *mirror_p = mirror;
>>> +    return NULL;
>>> +}
>>> +
>>> +static void
>>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>>> +{
>>> +    const char *port = ctx->argv[1];
>>> +    const char *mirror_name = ctx->argv[2];
>>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>>> +    const struct nbrec_mirror *mirror;
>>> +
>>> +    char *error;
>>> +
>>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +
>>> +    /*check if a mirror rule actually exists on that name or not*/
>>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    /* Check if same mirror rule already exists for the lsp */
>>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>>> +            bool may_exist = shash_find(&ctx->options, 
>>> "--may-exist") != NULL;
>>> +            if (!may_exist) {
>>> +                ctl_error(ctx, "Same mirror already existed on the 
>>> lsp %s.",
>>> +                          ctx->argv[1]);
>>> +                return;
>>> +            }
>>> +            return;
>>> +        }
>>> +    }
>>> +
>>> + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
>>> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
>>> +
>>> +}
>>> +
>>> +static void
>>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>>> +{
>>> +    const char *port = ctx->argv[1];
>>> +    const char *mirror_name = ctx->argv[2];
>>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>>> +    const struct nbrec_mirror *mirror;
>>> +
>>> +    char *error;
>>> +
>>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +
>>> +    /*check if a mirror rule actually exists on that name or not*/
>>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
>>> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
>>> +
>>> +}
>>> +
>>>   static void
>>>   nbctl_lsp_set_type(struct ctl_context *ctx)
>>>   {
>>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct 
>>> ctl_context *ctx)
>>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
>>>   }
>>>
>>> +static char * OVS_WARN_UNUSED_RESULT
>>> +parse_filter(const char *arg, const char **selection_p)
>>> +{
>>> +    /* Validate selection.  Only require the first letter. */
>>> +    if (arg[0] == 't') {
>>> +        *selection_p = "to-lport";
>>> +    } else if (arg[0] == 'f') {
>>> +        *selection_p = "from-lport";
>>> +    } else if (arg[0] == 'b') {
>>> +        *selection_p = "both";
>>> +    } else {
>>> +        *selection_p = NULL;
>>> +        return xasprintf("%s: selection must be \"to-lport\" or "
>>> +                         "\"from-lport\" or \"both\" ", arg);
>>> +    }
>>> +    return NULL;
>>> +}
>>> +
>>> +static char * OVS_WARN_UNUSED_RESULT
>>> +parse_type(const char *arg, const char **type_p)
>>> +{
>>> +    /* Validate type.  Only require the first letter. */
>>> +    if (arg[0] == 'g') {
>>> +        *type_p = "gre";
>>> +    } else if (arg[0] == 'e') {
>>> +        *type_p = "erspan";
>>> +    } else {
>>> +        *type_p = NULL;
>>> +        return xasprintf("%s: type must be \"gre\" or "
>>> +                         "\"erspan\"", arg);
>>> +    }
>>> +    return NULL;
>>> +}
>>> +
>>> +static void
>>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>> +}
>>> +
>>> +static void
>>> +nbctl_mirror_add(struct ctl_context *ctx)
>>> +{
>>> +    const char *filter = NULL;
>>> +    const char *sink_ip = NULL;
>>> +    const char *type = NULL;
>>> +    const char *name = NULL;
>>> +    char *new_sink_ip = NULL;
>>> +    int64_t index;
>>> +    char *error = NULL;
>>> +    const struct nbrec_mirror *mirror_check = NULL;
>>> +
>>> +    /* Mirror Name */
>>> +    name = ctx->argv[1];
>>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>>> +        if (!strcmp(mirror_check->name, name)) {
>>> +            ctl_error(ctx, "Mirror with %s name already exists.",
>>> +                      name);
>>> +            return;
>>> +        }
>>> +    }
>>> +
>>> +    /* Tunnel Type - GRE/ERSPAN */
>>> +    error = parse_type(ctx->argv[2], &type);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    /* tunnel index / GRE key / ERSPAN idx */
>>> +    index = atoi(ctx->argv[3]);
>> Shouldn't we validate the input is an actual number?
>>
>>> +
>>> +    /* Filter for mirroring */
>>> +    error = parse_filter(ctx->argv[4], &filter);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    /* Destination / Sink details */
>>> +    sink_ip = ctx->argv[5];
>>> +
>>> +    /* check if it is a valid ip */
>>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>>> +    if (!new_sink_ip) {
>>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>>> +    }
>>> +
>>> +    if (new_sink_ip) {
>>> +        free(new_sink_ip);
>>> +    } else {
>>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>>> +        return;
>>> +    }
>>> +
>>> +    /* Create the mirror. */
>>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>>> +    nbrec_mirror_set_name(mirror, name);
>>> +    nbrec_mirror_set_index(mirror, index);
>>> +    nbrec_mirror_set_filter(mirror, filter);
>>> +    nbrec_mirror_set_type(mirror, type);
>>> +    nbrec_mirror_set_sink(mirror, sink_ip);
>>> +
>>> +}
>>> +
>>> +static void
>>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>> +}
>>> +
>>> +static void
>>> +nbctl_mirror_del(struct ctl_context *ctx)
>>> +{
>>> +    const struct nbrec_mirror *mirror, *next;
>>> +
>>> +    /* If a name is not specified, delete all mirrors. */
>>> +    if (ctx->argc == 1) {
>>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>>> +            nbrec_mirror_delete(mirror);
>>> +        }
>>> +        return;
>>> +    }
>>> +
>>> +    /* Remove the matching mirror. */
>>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>> +        if (strcmp(ctx->argv[1], mirror->name)) {
>>> +            continue;
>>> +        }
>>> +        nbrec_mirror_delete(mirror);
>>> +        return;
>>> +    }
>>> +}
>>> +
>>> +static void
>>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, 
>>> &nbrec_logical_switch_port_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>> +}
>>> +
>>> +static void
>>> +nbctl_mirror_list(struct ctl_context *ctx)
>>> +{
>>> +
>>> +    const struct nbrec_mirror **mirrors = NULL;
>>> +    const struct nbrec_mirror *mirror;
>>> +    size_t n_capacity = 0;
>>> +    size_t n_mirrors = 0;
>>> +
>>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>> +        if (n_mirrors == n_capacity) {
>>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof 
>>> *mirrors);
>>> +        }
>>> +
>>> +        mirrors[n_mirrors] = mirror;
>>> +        n_mirrors++;
>>> +    }
>>> +
>>> +    if (n_mirrors) {
>>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>>> +    }
>>> +
>>> +    for (size_t i = 0; i < n_mirrors; i++) {
>>> +        mirror = mirrors[i];
>>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>>> +        /* print all the values */
>>> +        ds_put_format(&ctx->output, "  Type     : %s\n", 
>>> mirror->type);
>>> +        ds_put_format(&ctx->output, "  Sink     : %s\n", 
>>> mirror->sink);
>>> +        ds_put_format(&ctx->output, "  Filter   : %s\n", 
>>> mirror->filter);
>>> +        ds_put_format(&ctx->output, "  Index/Key: %ld\n",
>>> +                                                (long int) 
>>> mirror->index);
>> You don't ned to cast if you pass %d formatter instead of %ld. The 
>> same applies to other places in the patch where you cast to long int. 
>> In general, casting is not needed and should be avoided.
>>
>>> + ds_put_cstr(&ctx->output,   "  Sources  :");
>>> +        if (mirror->n_src > 0) {
>>> +            struct svec srcs;
>>> +            const char *src;
>>> +            size_t j;
>>> +            svec_init(&srcs);
>>> +            for (j = 0; j < mirror->n_src; j++) {
>>> +                svec_add(&srcs, mirror->src[j]->name);
>>> +            }
>>> +            svec_sort(&srcs);
>>> +            SVEC_FOR_EACH (j, src, &srcs) {
>>> +                ds_put_format(&ctx->output, "  %s", src);
>>> +            }
>>> +            svec_destroy(&srcs);
>>> +        } else {
>>> +            ds_put_cstr(&ctx->output, "  None attached");
>>> +        }
>>> +        ds_put_cstr(&ctx->output, "\n");
>>> +    }
>>> +
>>> +    free(mirrors);
>>> +}
>>> +
>>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
>>>       = {{&nbrec_logical_switch_port_col_name, NULL,
>>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax 
>>> nbctl_commands[] = {
>>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>>>         NULL, "", RO },
>>>
>>> +    /* mirror commands. */
>>> +    { "mirror-add", 5, 5,
>>> +      "NAME TYPE INDEX FILTER IP",
>>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", 
>>> RW },
>>> +    { "mirror-del", 0, 1, "[NAME]",
>>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, 
>>> nbctl_mirror_list,
>>> +      NULL, "", RO },
>>> +
>>>       /* meter commands. */
>>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", 
>>> nbctl_pre_meter_add,
>>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax 
>>> nbctl_commands[] = {
>>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, 
>>> nbctl_lsp_get_ls,
>>>         NULL, "", RO },
>>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
>>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>>>
>>>       /* forwarding group commands. */
>>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
>>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>>> index f60dde1b6..3d73e9e25 100644
>>> --- a/utilities/ovn-sbctl.c
>>> +++ b/utilities/ovn-sbctl.c
>>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>>> +    ovsdb_idl_add_column(ctx->idl, 
>>> &sbrec_port_binding_col_mirror_rules);
>>>
>>>       ovsdb_idl_add_column(ctx->idl, 
>>> &sbrec_logical_flow_col_logical_datapath);
>>>       ovsdb_idl_add_column(ctx->idl, 
>>> &sbrec_logical_flow_col_logical_dp_group);
>>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class 
>>> tables[SBREC_N_TABLES] = {
>>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>>>
>>> +    [SBREC_TABLE_MIRROR].row_ids[0]
>>> +    = {&sbrec_mirror_col_name, NULL, NULL},
>>> +
>>>       [SBREC_TABLE_METER].row_ids[0]
>>>       = {&sbrec_meter_col_name, NULL, NULL},
>>>
>>> -- 
>>> 2.31.1
>>>
Abhiram R N Nov. 16, 2022, 1:08 p.m. UTC | #4
Hi Ihar,

Thanks for your inputs. I think I have found the issue in the test.
In the test case I had missed one thing. Below are the details

The log which you shared as the last message in ovs-vswitchd log led me to
it.

> 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max >
translation depth 64 on bridge br-int while processing >
arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00

What I had missed is, the creation of hv2, setting ip to br-phys and ARP
resolving. (See code at the end for what I have added).
Once I added this now everything is passing fine. I don't see any failures.

Also everything works in a single test case. So, I will revert back to the
old method of having 1 bulk updates test case and verify all cases there
and with below code, will submit a patch soon.

Since in the bulk updates test case we were not verifying the actual packet
flow (already covered in a separate test) I had thought the below code was
not needed.
But that wasn't the case!

FYI, what I added now...

net_add n2

sim_add hv2
as hv2
ovs-vsctl add-br br-phys -- set bridge br-phys
other-config:hwaddr=\"00:00:00:02:02:00\"
ovn_attach n2 br-phys 192.168.1.12

OVN_POPULATE_ARP

Thanks & Regards,
Abhiram R N

On Wed, Nov 16, 2022 at 4:39 AM Ihar Hrachyshka <ihrachys@redhat.com> wrote:

> On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
> > I think there's a problem with the bulk tests added in this patch. I
> > will cover this issue in this email, and I'll send my code review
> > tomorrow as promised, since it's getting late here.
> >
> >
> > When running the whole suite locally, I get the following failures:
> >
> >
> > 401: Mirror test bulk swap attachments -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425)
> > 402: Mirror test bulk swap attachments -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425)
> > 403: Mirror test bulk attach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537)
> > 410: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no ok
> > 412: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no ok
> > 416: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no ok
> > 408: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650)
> > 417: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes ok
> > 409: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650)
> > 411: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> > 418: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no ok
> > 413: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> > 415: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879)
> > 414: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766)
> >
> >
> > Note that some of the test case variants passed, and I don't think
> > there's a clear pattern as to which of variants in the test matrix do
> > fail.
> >
> >
> > The error that triggers the failure is during ovs-vswitchd cleanup:
> >
> >
> > ./ovn.at:16425: ovs-appctl --timeout=10 -t ovs-vswitchd exit --cleanup
> > --- /dev/null   2022-11-04 04:09:25.869645998 +0000
> > +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr
> > 2022-11-15 16:31:15.557479369 +0000
> > @@ -0,0 +1,2 @@
> > +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with signal
> > 14 (Alarm clock)
> > +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source: line
> > 282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t
> > ovs-vswitchd exit --cleanup
> > ./ovn.at:16425: exit code was 142, expected 0
> >
> >
> > The very last message in ovs-vswitchd log on hv1 is exactly 10 seconds
> > before the alarm clock error:
> >
> >
> > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max
> > translation depth 64 on bridge br-int while processing
> >
> arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
> >
> >
> > I don't see coredumps generated for any of test processes, so it's
> > probably not the case of ovs-vswitchd crashing on exit request.
> >
> >
> > I tried to adjust your test cases to a minimal reproducer and I found
> > that if a test case creates two mirrors, both of to-lport type, then
> > ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl
> > requests, nor it handles new ports. But if I merely change the type of
> > one of mirrors in the test to from-lport, the test passes.
> >
> >
> > On the other hand, a consistent way to trigger the failure is adding a
> > 'sleep 3' at the end of a test case just before cleanup, apparently to
> > allow vswitchd to catch on the mirror updates and lock somewhere in
> > the code. I see vswitchd spinning at ~100% cpu in 'top' output when it
> > gets into this state. It's clearly doing SOMETHING, not just sleeping. :)
> >
> >
> > I suspect there's some bug inside vswitchd that makes it lock / spin
> > for a particular setup of mirrors. Whatever OVN sets up in vswitchd
> > database, the latter should not freeze. It would be helpful to provide
> > a short ovs-only reproducer for the situation that would not involve
> > OVN so that our OVS friends can take a look.
> >
> >
> > For the record, the mirrors in ovsdb are:
> >
> >
> > _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
> > external_ids        : {}
> > name                : mirror0
> > output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
> > output_vlan         : []
> > select_all          : false
> > select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab,
> > 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
> > select_src_port     : []
> > select_vlan         : []
> > snaplen             : []
> > statistics          : {}
> >
> > _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
> > external_ids        : {}
> > name                : mirror1
> > output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
> > output_vlan         : []
> > select_all          : false
> > select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6,
> > 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
> > select_src_port     : []
> > select_vlan         : []
> > snaplen             : []
> > statistics          : {}
> >
> >
> > Bridge output here:
> >
> >
> > 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
> >     Bridge br-int
> >         fail_mode: secure
> >         datapath_type: system
> >         Port vif4
> >             Interface vif4
> >         Port vif2
> >             Interface vif2
> >         Port br-int
> >             Interface br-int
> >                 type: internal
> >         Port vif1
> >             Interface vif1
> >         Port ovn-mirror0
> >             Interface ovn-mirror0
> >                 type: gre
> >                 options: {key="0", remote_ip="192.168.1.12"}
> >         Port vif3
> >             Interface vif3
> >         Port ovn-mirror1
> >             Interface ovn-mirror1
> >                 type: gre
> >                 options: {key="1", remote_ip="192.168.1.12"}
> >         Port patch-br-int-to-ln-public
> >             Interface patch-br-int-to-ln-public
> >                 type: patch
> >                 options: {peer=patch-ln-public-to-br-int}
> >     Bridge br-phys
> >         Port br-phys
> >             Interface br-phys
> >                 type: internal
> >                 options:
> >
> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap",
> > tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
> >         Port br-phys_n1
> >             Interface br-phys_n1
> >                 options:
> >
> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap",
>
> >
> stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock",
>
> >
> tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
>
> >
> >         Port patch-ln-public-to-br-int
> >             Interface patch-ln-public-to-br-int
> >                 type: patch
> >                 options: {peer=patch-br-int-to-ln-public}
> >
> Perhaps this may be of relevance: when I attach gdb to vswitchd process,
> I can see a never-ending stack of calls in thread 1 that looks like:
>
> #0  0x0000000000406760 in memcpy@plt ()
> #1  0x0000000000499d88 in dp_packet_clone_with_headroom
> (buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
> #2  0x000000000049bd4d in dp_packet_batch_clone (dst=0x7fffcbf54740,
> src=0x7fffcbf55230) at lib/dp-packet.h:863
> #3  0x00000000004b15ef in dp_execute_output_action (pmd=0x7f638115f010,
> packets_=0x7fffcbf55230, should_steal=false, port_no=3) at
> lib/dpif-netdev.c:8696
> #4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200,
> packets_=0x7fffcbf55230, a=0x7fffcbf555c0, should_steal=false) at
> lib/dpif-netdev.c:8787
> #5  0x0000000000507834 in odp_execute_actions (dp=0x7fffcbf55200,
> batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578,
> actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
>      at lib/odp-execute.c:993
> #6  0x00000000004b251e in dp_netdev_execute_actions (pmd=0x7f638115f010,
> packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60,
> actions=0x7fffcbf55578, actions_len=88)
>      at lib/dpif-netdev.c:9105
> #7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630,
> execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
> #8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630,
> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
> lib/dpif-netdev.c:4606
> #9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630,
> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
> lib/dpif.c:1372
> #10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630,
> execute=0x7fffcbf55500) at lib/dpif.c:1326
> #11 0x000000000043e46d in ofproto_dpif_execute_actions__
> (ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0,
> ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
>      packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
> #12 0x0000000000467e8a in compose_table_xlate (ctx=0x7fffcbf5f240,
> out_dev=0x1879430, packet=0x7fffcbf56050) at
> ofproto/ofproto-dpif-xlate.c:3526
> #13 0x0000000000468020 in tnl_send_arp_request (ctx=0x7fffcbf5f240,
> out_dev=0x1879430, eth_src=..., ip_src=184658112, ip_dst=201435328) at
> ofproto/ofproto-dpif-xlate.c:3555
> #14 0x0000000000468710 in native_tunnel_output (ctx=0x7fffcbf5f240,
> xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7, truncate=false,
> is_last_action=false)
>      at ofproto/ofproto-dpif-xlate.c:3721
> #15 0x000000000046a78e in compose_output_action__ (ctx=0x7fffcbf5f240,
> ofp_port=7, xr=0x0, check_stp=true, is_last_action=false,
> truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
> #16 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
> ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at
> ofproto/ofproto-dpif-xlate.c:4416
> #17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240,
> out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at
> ofproto/ofproto-dpif-xlate.c:2533
> #18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240,
> xbundle=0x1880d00, mirrors=2) at ofproto/ofproto-dpif-xlate.c:2190
> #19 0x000000000046a96b in compose_output_action__ (ctx=0x7fffcbf5f240,
> ofp_port=1, xr=0x0, check_stp=true, is_last_action=false,
> truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
> #20 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
> ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at
> ofproto/ofproto-dpif-xlate.c:4416
> #21 0x000000000046cf3e in xlate_output_action (ctx=0x7fffcbf5f240,
> port=1, controller_len=0, may_packet_in=true, is_last_action=false,
> truncate=false, group_bucket_action=false)
>      at ofproto/ofproto-dpif-xlate.c:5361
> #22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
> #23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
> rule=0x1860480, deepens=false, is_last_action=false,
> actions_xlator=0x470caf <do_xlate_actions>)
>      at ofproto/ofproto-dpif-xlate.c:4439
> #24 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
> in_port=5, table_id=65 'A', may_packet_in=false, honor_table_miss=false,
> with_ct_orig=false, is_last_action=false,
>      xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4568
> #25 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
> resubmit=0x185cfb8, is_last_action=false) at
> ofproto/ofproto-dpif-xlate.c:4879
> #26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
> rule=0x185ce00, deepens=false, is_last_action=false,
> actions_xlator=0x470caf <do_xlate_actions>)
>      at ofproto/ofproto-dpif-xlate.c:4439
> #28 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
> in_port=5, table_id=64 '@', may_packet_in=false, honor_table_miss=false,
> with_ct_orig=false, is_last_action=false,
>      xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4568
> #29 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
> resubmit=0x1834458, is_last_action=false) at
> ofproto/ofproto-dpif-xlate.c:4879
> #30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>
> it goes like that over and over and over for hundreds if not thousands
> of calls down the stack until
>
> #5738 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
> in_port=65533, table_id=9 '\t', may_packet_in=false,
> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
> #5739 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
> resubmit=0x1831ac0, is_last_action=true) at
> ofproto/ofproto-dpif-xlate.c:4879
> #5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68,
> ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #5741 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcc09b330,
> rule=0x18318b0, deepens=false, is_last_action=true,
> actions_xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4439
> #5742 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
> in_port=65533, table_id=8 '\b', may_packet_in=false,
> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
> #5743 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
> resubmit=0x187bcc0, is_last_action=true) at
> ofproto/ofproto-dpif-xlate.c:4879
> #5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80,
> ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0,
> xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
> #5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0,
> opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
> #5747 0x00000000004250d7 in ofproto_packet_out_start (ofproto=0x17c10e0,
> opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
> #5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40,
> oh=0x17fdfa0) at ofproto/ofproto.c:3764
> #5749 0x000000000042fce2 in handle_single_part_openflow
> (ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at
> ofproto/ofproto.c:8664
> #5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40,
> msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
> #5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40,
> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:1329
> #5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0,
> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:356
> #5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at
> ofproto/ofproto.c:1933
> #5754 0x00000000004101d4 in bridge_run__ () at vswitchd/bridge.c:3210
> #5755 0x00000000004103d3 in bridge_run () at vswitchd/bridge.c:3269
> #5756 0x0000000000415cf0 in main (argc=10, argv=0x7fffcc09dff8) at
> vswitchd/ovs-vswitchd.c:129
>
> The main thread never getting out of some processing code to reach any
> other handlers (e.g. for appctl requests?)
>
> I'm adding Ilya to CC in case he has an idea why vswitchd could lock /
> freeze / spin indefinitely on two to-lport mirror creation.
>
>
> > On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
> >> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N <abhiramrn@gmail.com> wrote:
> >>> Mirror creation just creates the mirror. The lsp-attach-mirror
> >>> triggers the sequence to create Mirror in OVS DB on compute node.
> >>> OVS already supports Port Mirroring.
> >>>
> >>> Note: This is targeted to mirror to destinations anywhere outside the
> >>> cluster where the analyser resides and it need not be an OVN node.
> >>>
> >>> Example commands are as below:
> >>>
> >>> Mirror creation
> >>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
> >>>
> >>> Attach a logical port to the mirror.
> >>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
> >>>
> >>> Detach a source from Mirror
> >>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
> >>>
> >>> Mirror deletion
> >>> ovn-nbctl mirror-del mirror1
> >>>
> >>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> >>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> >>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
> >>> ---
> >>> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
> >>>               test to make it pass consistently.
> >>> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
> >>>
> >>> V10 --> V11: Addressed review comments from V10 by Ihar
> >>>             i) Expanded bulk updates test cases in ovn.at
> >>>                Overall below cases are covered
> >>>                a) Attaches multiple mirrors (new)
> >>>                b) Equal detaches and attaches (same as V10)
> >>>                c) Detaches more than attaches (new)
> >>>                d) Attaches more than detaches (new)
> >>>                e) Detaches all (new)
> >>>            ii) Addressed the detach all case in mirror.c
> >>>           iii) Minor correction in NEWS
> >>>            iv) Added invalid mirror attach case in ovn-nbctl.at
> >>>
> >>> Files modified (V10 --> V11):
> >>> Code --> mirror.c
> >>> Test --> ovn.at, ovn-nbctl.at
> >>> Misc --> NEWS
> >>>
> >>> Ihar,
> >>>      Regarding mirror_delete function param delete_all it is wrt the
> >>> port binding and if a port binding is removed we delete all its
> >>> attachment. Already that use case is covered in ovn.at.
> >>> Having said that the detaches all had issue in mirror_delete which
> >>> I have addressed. With all the above cases added now in bulk updates
> >>> hope it should give good assurance.
> >>>
> >>>   NEWS                        |   1 +
> >>>   controller/automake.mk      |   4 +-
> >>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
> >>>   controller/mirror.h         |  53 +++
> >>>   controller/ovn-controller.c | 266 ++++++++++--
> >>>   northd/en-northd.c          |   4 +
> >>>   northd/inc-proc-northd.c    |   4 +
> >>>   northd/northd.c             | 172 ++++++++
> >>>   northd/northd.h             |   2 +
> >>>   ovn-nb.ovsschema            |  31 +-
> >>>   ovn-nb.xml                  |  63 +++
> >>>   ovn-sb.ovsschema            |  26 +-
> >>>   ovn-sb.xml                  |  50 +++
> >>>   tests/ovn-nbctl.at          | 120 ++++++
> >>>   tests/ovn-northd.at         | 102 +++++
> >>>   tests/ovn.at                | 778
> >>> ++++++++++++++++++++++++++++++++++++
> >>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
> >>>   utilities/ovn-sbctl.c       |   4 +
> >>>   18 files changed, 2547 insertions(+), 28 deletions(-)
> >>>   create mode 100644 controller/mirror.c
> >>>   create mode 100644 controller/mirror.h
> >>>
> >>> diff --git a/NEWS b/NEWS
> >>> index 224a7b83e..84b22abdb 100644
> >>> --- a/NEWS
> >>> +++ b/NEWS
> >>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
> >>>       any of LR's LRP IP, there is no need to create SNAT entry.
> >>> Now such
> >>>       traffic destined to LRP IP is not dropped.
> >>>     - Bump python version required for building OVN to 3.6.
> >>> +  - Added Support for Remote Port Mirroring.
> >>>
> >>>   OVN v22.06.0 - 03 Jun 2022
> >>>   --------------------------
> >>> diff --git a/controller/automake.mk b/controller/automake.mk
> >>> index c2ab1bbe6..334672b4d 100644
> >>> --- a/controller/automake.mk
> >>> +++ b/controller/automake.mk
> >>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
> >>>          controller/ovsport.h \
> >>>          controller/ovsport.c \
> >>>          controller/vif-plug.h \
> >>> -       controller/vif-plug.c
> >>> +       controller/vif-plug.c \
> >>> +       controller/mirror.h \
> >>> +       controller/mirror.c
> >>>
> >>>   controller_ovn_controller_LDADD = lib/libovn.la
> >>> $(OVS_LIBDIR)/libopenvswitch.la
> >>>   man_MANS += controller/ovn-controller.8
> >>> diff --git a/controller/mirror.c b/controller/mirror.c
> >>> new file mode 100644
> >>> index 000000000..11f2b63a6
> >>> --- /dev/null
> >>> +++ b/controller/mirror.c
> >>> @@ -0,0 +1,538 @@
> >>> +/* Copyright (c) 2022 Red Hat, Inc.
> >>> + *
> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
> >>> + * you may not use this file except in compliance with the License.
> >>> + * You may obtain a copy of the License at:
> >>> + *
> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
> >>> + *
> >>> + * Unless required by applicable law or agreed to in writing, software
> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> >>> implied.
> >>> + * See the License for the specific language governing permissions and
> >>> + * limitations under the License.
> >>> + */
> >>> +
> >>> +#include <config.h>
> >>> +#include <unistd.h>
> >>> +
> >>> +/* library headers */
> >>> +#include "lib/sset.h"
> >>> +#include "lib/util.h"
> >>> +
> >>> +/* OVS includes. */
> >>> +#include "lib/vswitch-idl.h"
> >>> +#include "openvswitch/vlog.h"
> >>> +
> >>> +/* OVN includes. */
> >>> +#include "binding.h"
> >>> +#include "lib/ovn-sb-idl.h"
> >>> +#include "mirror.h"
> >>> +
> >>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> >>> +
> >>> +/* Static function declarations */
> >>> +
> >>> +static const struct ovsrec_port *
> >>> +get_port_for_iface(const struct ovsrec_interface *iface,
> >>> +                  const struct ovsrec_bridge *br_int)
> >>> +{
> >>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> >>> +        const struct ovsrec_port *p = br_int->ports[i];
> >>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> >>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> >>> +                return p;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static bool
> >>> +mirror_create(const struct sbrec_port_binding *pb,
> >>> +              struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct ovsrec_mirror *mirror = NULL;
> >>> +
> >>> +    if (pb->n_up && !pb->up[0]) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    if (pb->chassis != pm_ctx->chassis_rec) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    if (!pm_ctx->ovs_idl_txn) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +
> >>> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> >>> +    /* Loop through the mirror rules */
> >>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> >>> +        /* check if the mirror already exists in OVS DB */
> >>> +        bool create_mirror = true;
> >>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> >>> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
> >>> +                /* Mirror with same name already exists
> >>> +                 * No need to create mirror
> >>> +                 */
> >>> +                create_mirror = false;
> >>> +                break;
> >>> +            }
> >>> +        }
> >>> +
> >>> +        if (create_mirror) {
> >>> +
> >>> +            struct smap options = SMAP_INITIALIZER(&options);
> >>> +            char *port_name, *key;
> >>> +
> >>> +            key = xasprintf("%ld",(long int)
> >>> pb->mirror_rules[i]->index);
> >>> +            smap_add(&options, "remote_ip",
> >>> pb->mirror_rules[i]->sink);
> >>> +            smap_add(&options, "key", key);
> >>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> >>> +                /* Set the ERSPAN index */
> >>> +                smap_add(&options, "erspan_idx", key);
> >>> +                smap_add(&options, "erspan_ver","1");
> >>> +
> >>> +            }
> >>> +            struct ovsrec_interface *iface =
> >>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> >>> +            port_name = xasprintf("ovn-%s",
> >>> + pb->mirror_rules[i]->name);
> >>> +
> >>> +            ovsrec_interface_set_name(iface, port_name);
> >>> +            ovsrec_interface_set_type(iface,
> >>> pb->mirror_rules[i]->type);
> >>> +            ovsrec_interface_set_options(iface, &options);
> >>> +
> >>> +            struct ovsrec_port *port =
> >>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> >>> +            ovsrec_port_set_name(port, port_name);
> >>> +            ovsrec_port_set_interfaces(port, &iface, 1);
> >>> +
> >>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> >>> +
> >>> +            smap_destroy(&options);
> >>> +            free(port_name);
> >>> +            free(key);
> >>> +
> >>> +            VLOG_INFO("Creating Mirror in OVS DB");
> >>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> >>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> >>> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> >>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> >>> + mirror);
> >>> +        }
> >>> +
> >>> +        struct local_binding *lbinding = local_binding_find(
> >>> +                               pm_ctx->local_bindings,
> >>> pb->logical_port);
> >>> +        const struct ovsrec_port *p =
> >>> +                     get_port_for_iface(lbinding->iface,
> >>> pm_ctx->br_int);
> >>> +        if (p) {
> >>> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> >>> +            } else if
> >>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> >>> +            } else {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static void
> >>> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> >>> +                              struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    char *filter;
> >>> +    if ((ovs_mirror->n_select_dst_port)
> >>> +            && (ovs_mirror->n_select_src_port)) {
> >>> +        filter = "both";
> >>> +    } else if (ovs_mirror->n_select_dst_port) {
> >>> +        filter = "to-lport";
> >>> +    } else {
> >>> +        filter = "from-lport";
> >>> +    }
> >>> +
> >>> +    if (strcmp(sb_mirror->filter, filter)) {
> >>> +        if (!strcmp(sb_mirror->filter,"from-lport")
> >>> +                              && !strcmp(filter,"both")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> >>> +                              && !strcmp(filter,"both")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
> >>> +                              && !strcmp(filter,"from-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
> >>> +                              && !strcmp(filter,"to-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> >>> +                              && !strcmp(filter,"from-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> >>> +                              && !strcmp(filter,"to-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> >>> +                                   struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    struct smap options = SMAP_INITIALIZER(&options);
> >>> +    char *key, *type;
> >>> +    struct ovsrec_interface *iface =
> >>> + ovs_mirror->output_port->interfaces[0];
> >>> +    struct smap *opts = &iface->options;
> >>> +
> >>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> >>> +    if (erspan_ver) {
> >>> +        type = "erspan";
> >>> +    } else {
> >>> +        type = "gre";
> >>> +    }
> >>> +    if (strcmp(type, sb_mirror->type)) {
> >>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> >>> +    }
> >>> +
> >>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
> >>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> >>> +    smap_add(&options, "key", key);
> >>> +
> >>> +    if (!strcmp(sb_mirror->type, "erspan")) {
> >>> +        /* Set the ERSPAN index */
> >>> +        smap_add(&options, "erspan_idx", key);
> >>> +        smap_add(&options, "erspan_ver","1");
> >>> +    }
> >>> +
> >>> +    ovsrec_interface_set_options(iface, &options);
> >>> +    smap_destroy(&options);
> >>> +    free(key);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +mirror_update(const struct sbrec_mirror *sb_mirror,
> >>> +              struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> >>> +
> >>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> >>> +}
> >>> +
> >>> +static bool
> >>> +mirror_delete(const struct sbrec_port_binding *pb,
> >>> +              struct port_mirror_ctx *pm_ctx,
> >>> +              struct shash *pb_mirror_map,
> >>> +              bool delete_all)
> >>> +{
> >>> +
> >>> +    if (!pm_ctx->ovs_idl_txn) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> >>> +
> >>> +    if (!delete_all) {
> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> >>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> >>> +        }
> >>> +    }
> >>> +
> >>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
> >>> pb->n_mirror_rules) {
> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> >>> +
> >>> +            struct ovsrec_mirror *ovs_mirror = NULL;
> >>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> >>> + pb->mirror_rules[i]->name);
> >>> +            if (ovs_mirror) {
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    struct shash_node *mirror_node;
> >>> +    const struct sbrec_port_binding *sb_pb;
> >>> +    int attach_cnt = 0;
> >>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> >>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> >>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> >>> +            /* Find if the mirror has other sources */
> >>> +            attach_cnt = 0;
> >>> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
> >>> + pm_ctx->port_binding_table) {
> >>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
> >>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
> >>> + ovs_mirror->name)) {
> >>> +                        attach_cnt++;
> >>> +                    }
> >>> +                }
> >>> +            }
> >>> +            if (attach_cnt) {
> >>> +                /* More than 1 source then just
> >>> +                 * update the mirror table
> >>> +                 */
> >>> +                bool done = false;
> >>> +                for (size_t i = 0; ((i <
> >>> ovs_mirror->n_select_dst_port)
> >>> +                                                   && (done ==
> >>> false)); i++) {
> >>> +                    const struct ovsrec_port *port_rec =
> >>> + ovs_mirror->select_dst_port[i];
> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
> >>> j++) {
> >>> +                        const struct ovsrec_interface *iface_rec;
> >>> +
> >>> +                        iface_rec = port_rec->interfaces[j];
> >>> +                        const char *iface_id =
> >>> + smap_get(&iface_rec->external_ids,
> >>> + "iface-id");
> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(
> >>> + ovs_mirror, port_rec);
> >>> +                            done = true;
> >>> +                            break;
> >>> +                        }
> >>> +                    }
> >>> +                }
> >>> +                done = false;
> >>> +                for (size_t i = 0; ((i <
> >>> ovs_mirror->n_select_src_port)
> >>> +                                                   && (done ==
> >>> false)); i++) {
> >>> +                    const struct ovsrec_port *port_rec =
> >>> + ovs_mirror->select_src_port[i];
> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
> >>> j++) {
> >>> +                        const struct ovsrec_interface *iface_rec;
> >>> +
> >>> +                        iface_rec = port_rec->interfaces[j];
> >>> +                        const char *iface_id =
> >>> + smap_get(&iface_rec->external_ids,
> >>> + "iface-id");
> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
> >>> + ovsrec_mirror_update_select_src_port_delvalue(
> >>> + ovs_mirror, port_rec);
> >>> +                            done = true;
> >>> +                            break;
> >>> +                        }
> >>> +                    }
> >>> +                }
> >>> +            } else {
> >>> +                /*
> >>> +                 * If only 1 source delete the output port
> >>> +                 * and then delete the mirror completely
> >>> +                 */
> >>> +                VLOG_INFO("Only 1 source for the mirror. Hence
> >>> delete it");
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    const char *used_node, *used_next;
> >>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> >>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> >>> +    }
> >>> +    sset_destroy(&pb_mirrors);
> >>> +
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static void
> >>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> >>> +                            struct port_mirror_ctx *pm_ctx,
> >>> +                            struct shash *pb_mirror_map)
> >>> +{
> >>> +    const struct ovsrec_mirror *mirror = NULL;
> >>> +
> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> >>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> >>> +            const struct ovsrec_port *port_rec =
> >>> mirror->select_dst_port[i];
> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> >>> +                const struct ovsrec_interface *iface_rec;
> >>> +                iface_rec = port_rec->interfaces[j];
> >>> +                const char *logical_port =
> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
> >>> mirror);
> >>> +                }
> >>> +            }
> >>> +        }
> >>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> >>> +            const struct ovsrec_port *port_rec =
> >>> mirror->select_src_port[i];
> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> >>> +                const struct ovsrec_interface *iface_rec;
> >>> +                iface_rec = port_rec->interfaces[j];
> >>> +                const char *logical_port =
> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
> >>> mirror);
> >>> +                }
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +void
> >>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>> +{
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> >>> +
> >>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> >>> +}
> >>> +
> >>> +
> >>> +void
> >>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> >>> +{
> >>> +    shash_init(ovs_mirrors);
> >>> +}
> >>> +
> >>> +void
> >>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct sbrec_port_binding *pb;
> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> >>> + pm_ctx->port_binding_table) {
> >>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> >>> +    }
> >>> +}
> >>> +
> >>> +bool
> >>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> >>> bool removed,
> >>> +                     struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    bool ret = true;
> >>> +    struct local_binding *lbinding = local_binding_find(
> >>> +                               pm_ctx->local_bindings,
> >>> pb->logical_port);
> >>> +
> >>> +    if (strcmp(pb->type, "") && (!lbinding)) {
> >>> +        return ret;
> >>> +    }
> >>> +
> >>> +    struct shash port_ovs_mirrors =
> >>> SHASH_INITIALIZER(&port_ovs_mirrors);
> >>> +
> >>> +    /* Need to find if mirror needs update */
> >>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> >>> +    if (!removed) {
> >>> +        if ((pb->n_mirror_rules == 0)
> >>> +              && (shash_is_empty(&port_ovs_mirrors))) {
> >>> +            /* No mirror update */
> >>> +        } else if (pb->n_mirror_rules ==
> >>> shash_count(&port_ovs_mirrors)) {
> >>> +            /* Though number of mirror rules are same,
> >>> +             * need to verify the contents
> >>> +             */
> >>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> >>> +                if (!shash_find(&port_ovs_mirrors,
> >>> + pb->mirror_rules[i]->name)) {
> >>> +                    /* Mis match in OVN SB DB and OVS DB
> >>> +                     * Delete and Create mirror(s) with proper sources
> >>> +                     */
> >>> +                    ret = mirror_delete(pb, pm_ctx,
> >>> + &port_ovs_mirrors, false);
> >>> +                    if (ret) {
> >>> +                        ret = mirror_create(pb, pm_ctx);
> >>> +                    }
> >>> +                    break;
> >>> +                }
> >>> +            }
> >>> +        } else {
> >>> +            /* Update Mirror */
> >>> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> >>> +                /* create mirror,
> >>> +                 * if mirror already exists only update selection
> >>> +                 */
> >>> +                ret = mirror_create(pb, pm_ctx);
> >>> +            } else {
> >>> +                /* delete mirror,
> >>> +                 * if mirror has other sources only update selection
> >>> +                 */
> >>> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors,
> >>> false);
> >>> +            }
> >>> +        }
> >>> +    } else {
> >>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
> >>> +    }
> >>> +
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> + &port_ovs_mirrors) {
> >>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +    shash_destroy(&port_ovs_mirrors);
> >>> +
> >>> +    return ret;
> >>> +}
> >>> +
> >>> +bool
> >>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct sbrec_mirror *mirror = NULL;
> >>> +    struct ovsrec_mirror *ovs_mirror = NULL;
> >>> +
> >>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
> >>> pm_ctx->sb_mirror_table) {
> >>> +    /* For each tracked mirror entry check if OVS entry is there*/
> >>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> >>> mirror->name);
> >>> +        if (ovs_mirror) {
> >>> +            if (sbrec_mirror_is_deleted(mirror)) {
> >>> +                /* Need to delete the mirror in OVS */
> >>> +                VLOG_INFO("Delete mirror and remove port");
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            } else {
> >>> +                mirror_update(mirror, ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    return true;
> >>> +}
> >>> +
> >>> +void
> >>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> >>> +{
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> +                                              ovs_mirrors) {
> >>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +    shash_destroy(ovs_mirrors);
> >>> +}
> >>> diff --git a/controller/mirror.h b/controller/mirror.h
> >>> new file mode 100644
> >>> index 000000000..85b964f55
> >>> --- /dev/null
> >>> +++ b/controller/mirror.h
> >>> @@ -0,0 +1,53 @@
> >>> +/* Copyright (c) 2022 Red Hat, Inc.
> >>> + *
> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
> >>> + * you may not use this file except in compliance with the License.
> >>> + * You may obtain a copy of the License at:
> >>> + *
> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
> >>> + *
> >>> + * Unless required by applicable law or agreed to in writing, software
> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> >>> implied.
> >>> + * See the License for the specific language governing permissions and
> >>> + * limitations under the License.
> >>> + */
> >>> +
> >>> +#ifndef OVN_MIRROR_H
> >>> +#define OVN_MIRROR_H 1
> >>> +
> >>> +struct ovsdb_idl_txn;
> >>> +struct ovsrec_port_table;
> >>> +struct ovsrec_bridge;
> >>> +struct ovsrec_bridge_table;
> >>> +struct ovsrec_open_vswitch_table;
> >>> +struct sbrec_chassis;
> >>> +struct ovsrec_interface_table;
> >>> +struct ovsrec_mirror_table;
> >>> +struct sbrec_mirror_table;
> >>> +struct sbrec_port_binding_table;
> >>> +
> >>> +struct port_mirror_ctx {
> >>> +    struct shash *ovs_mirrors;
> >>> +    struct ovsdb_idl_txn *ovs_idl_txn;
> >>> +    const struct ovsrec_port_table *port_table;
> >>> +    const struct ovsrec_bridge *br_int;
> >>> +    const struct sbrec_chassis *chassis_rec;
> >>> +    const struct ovsrec_bridge_table *bridge_table;
> >>> +    const struct ovsrec_open_vswitch_table *ovs_table;
> >>> +    const struct ovsrec_interface_table *iface_table;
> >>> +    const struct ovsrec_mirror_table *mirror_table;
> >>> +    const struct sbrec_mirror_table *sb_mirror_table;
> >>> +    const struct sbrec_port_binding_table *port_binding_table;
> >>> +    struct shash *local_bindings;
> >>> +};
> >>> +
> >>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> >>> +void ovn_port_mirror_init(struct shash *);
> >>> +void ovn_port_mirror_destroy(struct shash *);
> >>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> >>> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> >>> +                                  bool removed,
> >>> +                                  struct port_mirror_ctx *pm_ctx);
> >>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> >>> +#endif
> >>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> >>> index 8895c7a2b..15ab17c4a 100644
> >>> --- a/controller/ovn-controller.c
> >>> +++ b/controller/ovn-controller.c
> >>> @@ -78,6 +78,7 @@
> >>>   #include "lib/inc-proc-eng.h"
> >>>   #include "lib/ovn-l7.h"
> >>>   #include "hmapx.h"
> >>> +#include "mirror.h"
> >>>
> >>>   VLOG_DEFINE_THIS_MODULE(main);
> >>>
> >>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
> >>>       ovsdb_idl_track_add_column(ovs_idl,
> >>> &ovsrec_port_col_external_ids);
> >>> +    mirror_register_ovs_idl(ovs_idl);
> >>>   }
> >>>
> >>>   #define SB_NODES \
> >>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>>       SB_NODE(load_balancer, "load_balancer") \
> >>>       SB_NODE(fdb, "fdb") \
> >>>       SB_NODE(meter, "meter") \
> >>> +    SB_NODE(mirror, "mirror") \
> >>>       SB_NODE(static_mac_binding, "static_mac_binding")
> >>>
> >>>   enum sb_engine_node {
> >>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
> >>>       OVS_NODE(bridge, "bridge") \
> >>>       OVS_NODE(port, "port") \
> >>>       OVS_NODE(interface, "interface") \
> >>> -    OVS_NODE(qos, "qos")
> >>> +    OVS_NODE(qos, "qos") \
> >>> +    OVS_NODE(mirror, "mirror")
> >>>
> >>>   enum ovs_engine_node {
> >>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> >>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
> >>>       free(lbs);
> >>>   }
> >>>
> >>> +/* Mirror Engine */
> >>> +struct ed_type_port_mirror {
> >>> +    struct shash ovs_mirrors;
> >>> +};
> >>> +
> >>> +static void *
> >>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> >>> +                    struct engine_arg *arg OVS_UNUSED)
> >>> +{
> >>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof
> >>> *port_mirror);
> >>> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> >>> +    return port_mirror;
> >>> +}
> >>> +
> >>> +static void
> >>> +en_port_mirror_cleanup(void *data)
> >>> +{
> >>> +    struct ed_type_port_mirror *port_mirror = data;
> >>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> >>> +}
> >>> +
> >>> +static void
> >>> +init_port_mirror_ctx(struct engine_node *node,
> >>> +                 struct ed_type_runtime_data *rt_data,
> >>> +                 struct ed_type_port_mirror *port_mirror_data,
> >>> +                 struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    struct ovsrec_open_vswitch_table *ovs_table =
> >>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_open_vswitch", node));
> >>> +    struct ovsrec_bridge_table *bridge_table =
> >>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_bridge", node));
> >>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> >>> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> >>> ovs_table);
> >>> +
> >>> +    ovs_assert(br_int && chassis_id);
> >>> +    const struct sbrec_chassis *chassis = NULL;
> >>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> >>> +        engine_ovsdb_node_get_index(
> >>> +                engine_get_input("SB_chassis", node),
> >>> +                "name");
> >>> +
> >>> +    if (chassis_id) {
> >>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name,
> >>> chassis_id);
> >>> +    }
> >>> +    ovs_assert(chassis);
> >>> +
> >>> +    struct ovsrec_port_table *port_table =
> >>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_port", node));
> >>> +
> >>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> >>> +        engine_get_input_data("ovs_interface_shadow", node);
> >>> +
> >>> +    struct ovsrec_mirror_table *mirror_table =
> >>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_mirror", node));
> >>> +
> >>> +    struct sbrec_port_binding_table *pb_table =
> >>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("SB_port_binding", node));
> >>> +
> >>> +    struct sbrec_mirror_table *sb_mirror_table =
> >>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("SB_mirror", node));
> >>> +
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> + &port_mirror_data->ovs_mirrors) {
> >>> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +
> >>> +    const struct ovsrec_mirror *ovsmirror = NULL;
> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> >>> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
> >>> ovsmirror);
> >>> +    }
> >>> +
> >>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> >>> +    pm_ctx->port_table = port_table;
> >>> +    pm_ctx->iface_table = iface_shadow->iface_table;
> >>> +    pm_ctx->mirror_table = mirror_table;
> >>> +    pm_ctx->port_binding_table = pb_table;
> >>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> >>> +    pm_ctx->br_int = br_int;
> >>> +    pm_ctx->chassis_rec = chassis;
> >>> +    pm_ctx->bridge_table = bridge_table;
> >>> +    pm_ctx->ovs_table = ovs_table;
> >>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> >>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> >>> +}
> >>> +
> >>> +static void
> >>> +en_port_mirror_run(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    ovn_port_mirror_run(&pm_ctx);
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    /* There is no tracked data. Fall back to full recompute of
> >>> port_mirror */
> >>> +    if (!rt_data->tracked) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> >>> +    if (hmap_is_empty(tracked_dp_bindings)) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    struct tracked_datapath *tdp;
> >>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> >>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> >>> +            /* Fall back to full recompute when a local datapath
> >>> +             * is added or deleted. */
> >>> +            return false;
> >>> +        }
> >>> +
> >>> +        struct shash_node *shash_node;
> >>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> >>> +            struct tracked_lport *lport = shash_node->data;
> >>> +            bool removed =
> >>> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ?
> >>> true: false;
> >>> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed,
> >>> &pm_ctx)) {
> >>> +                return false;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    /* handle port binding updates (i.,e when the mirror column
> >>> +     * of port_binding is updated)
> >>> +     */
> >>> +    const struct sbrec_port_binding *pb;
> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >>> + pm_ctx.port_binding_table) {
> >>> +        bool removed = sbrec_port_binding_is_deleted(pb);
> >>> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> >>> +            return false;
> >>> +        }
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    /* handle sb mirror updates
> >>> +     */
> >>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +
> >>> +}
> >>> +
> >>>   /* Engine node which is used to handle the Non VIF data like
> >>>    *   - OVS patch ports
> >>>    *   - Tunnel ports and the related chassis information.
> >>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
> >>>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
> >>>       ENGINE_NODE(northd_options, "northd_options");
> >>>       ENGINE_NODE(dhcp_options, "dhcp_options");
> >>> +    ENGINE_NODE(port_mirror, "port_mirror");
> >>>
> >>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> >>>       SB_NODES
> >>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
> >>>       engine_add_input(&en_flow_output, &en_pflow_output,
> >>>                        flow_output_pflow_output_handler);
> >>>
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_port,
> >>> engine_noop_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> >>> +                     engine_noop_handler);
> >>> +    engine_add_input(&en_flow_output, &en_port_mirror,
> >>> +                     engine_noop_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> >>> +                     port_mirror_runtime_data_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> >>> +                     port_mirror_sb_mirror_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> >>> +                     port_mirror_port_binding_handler);
> >>> +
> >>>       struct engine_arg engine_arg = {
> >>>           .sb_idl = ovnsb_idl_loop.idl,
> >>>           .ovs_idl = ovs_idl_loop.idl,
> >>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
> >>>
> >>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
> >>>                                       time_msec());
> >>> -                    if (ovnsb_idl_txn) {
> >>> -                        if (ofctrl_has_backlog()) {
> >>> -                            /* When there are in-flight messages
> >>> pending to
> >>> -                             * ovs-vswitchd, we should hold on
> >>> recomputing so
> >>> -                             * that the previous flow installations
> >>> won't be
> >>> -                             * delayed.  However, we still want to
> >>> try if
> >>> -                             * recompute is not needed and we can
> >>> quickly
> >>> -                             * incrementally process the new
> >>> changes, to avoid
> >>> -                             * unnecessarily forced recomputes
> >>> later on.  This
> >>> -                             * is because the OVSDB change tracker
> >>> cannot
> >>> -                             * preserve tracked changes across
> >>> iterations.  If
> >>> -                             * change tracking is improved, we can
> >>> simply skip
> >>> -                             * this round of engine_run and
> >>> continue processing
> >>> -                             * acculated changes incrementally
> >>> later when
> >>> -                             * ofctrl_has_backlog() returns false. */
> >>> -                            engine_run(false);
> >>> -                        } else {
> >>> -                            engine_run(true);
> >>> -                        }
> >>> -                    } else {
> >>> -                        /* Even if there's no SB DB transaction
> >>> available,
> >>> +
> >>> +                    bool allow_engine_recompute = true;
> >>> +
> >>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
> >>> + ofctrl_has_backlog()) {
> >>> +                        /* When there are in-flight messages
> >>> pending to
> >>> +                         * ovs-vswitchd, we should hold on
> >>> recomputing so
> >>> +                         * that the previous flow installations
> >>> won't be
> >>> +                         * delayed.  However, we still want to try if
> >>> +                         * recompute is not needed and we can quickly
> >>> +                         * incrementally process the new changes,
> >>> to avoid
> >>> +                         * unnecessarily forced recomputes later
> >>> on.  This
> >>> +                         * is because the OVSDB change tracker cannot
> >>> +                         * preserve tracked changes across
> >>> iterations.  If
> >>> +                         * change tracking is improved, we can
> >>> simply skip
> >>> +                         * this round of engine_run and continue
> >>> processing
> >>> +                         * acculated changes incrementally later when
> >>> +                         * ofctrl_has_backlog() returns false. */
> >>> +
> >>> +                        /* Even if there's no SB/OVS DB transaction
> >>> available,
> >>>                            * try to run the engine so that we can
> >>> handle any
> >>>                            * incremental changes that don't require
> >>> a recompute.
> >>>                            * If a recompute is required, the engine
> >>> will abort,
> >>>                            * triggerring a full run in the next
> >>> iteration.
> >>>                            */
> >>> -                        engine_run(false);
> >>> +                        allow_engine_recompute = false;
> >>>                       }
> >>> +
> >>> +                    engine_run(allow_engine_recompute);
> >>> +
> >>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
> >>>                                      time_msec());
> >>>                       if (engine_has_updated()) {
> >>> diff --git a/northd/en-northd.c b/northd/en-northd.c
> >>> index 7fe83db64..608220b1f 100644
> >>> --- a/northd/en-northd.c
> >>> +++ b/northd/en-northd.c
> >>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void
> >>> *data)
> >>>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
> >>>       input_data.nbrec_static_mac_binding_table =
> >>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> >>> +    input_data.nbrec_mirror_table =
> >>> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
> >>>
> >>>       input_data.sbrec_sb_global_table =
> >>>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> >>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node,
> >>> void *data)
> >>>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
> >>>       input_data.sbrec_static_mac_binding_table =
> >>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> >>> +    input_data.sbrec_mirror_table =
> >>> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
> >>>
> >>>       northd_run(&input_data, data,
> >>>                  eng_ctx->ovnnb_idl_txn,
> >>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> >>> index 54e0ad3b0..ac27a730e 100644
> >>> --- a/northd/inc-proc-northd.c
> >>> +++ b/northd/inc-proc-northd.c
> >>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >>>       NB_NODE(acl, "acl") \
> >>>       NB_NODE(logical_router, "logical_router") \
> >>>       NB_NODE(qos, "qos") \
> >>> +    NB_NODE(mirror, "mirror") \
> >>>       NB_NODE(meter, "meter") \
> >>>       NB_NODE(meter_band, "meter_band") \
> >>>       NB_NODE(logical_router_port, "logical_router_port") \
> >>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >>>       SB_NODE(logical_flow, "logical_flow") \
> >>>       SB_NODE(logical_dp_group, "logical_DP_group") \
> >>>       SB_NODE(multicast_group, "multicast_group") \
> >>> +    SB_NODE(mirror, "mirror") \
> >>>       SB_NODE(meter, "meter") \
> >>>       SB_NODE(meter_band, "meter_band") \
> >>>       SB_NODE(datapath_binding, "datapath_binding") \
> >>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
> >>> *nb,
> >>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
> >>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> >>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
> >>> *nb,
> >>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> >>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> >>> diff --git a/northd/northd.c b/northd/northd.c
> >>> index b7388afc5..52abdda28 100644
> >>> --- a/northd/northd.c
> >>> +++ b/northd/northd.c
> >>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
> >>>       free(requested_chassis_sb);
> >>>   }
> >>>
> >>> +static void
> >>> +do_sb_mirror_addition(struct northd_input *input_data,
> >>> +                      const struct ovn_port *op)
> >>> +{
> >>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +        const struct sbrec_mirror *sb_mirror;
> >>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> >>> + input_data->sbrec_mirror_table) {
> >>> +            if (!strcmp(sb_mirror->name,
> >>> + op->nbsp->mirror_rules[i]->name)) {
> >>> +                /* Add the value to SB */
> >>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> >>> + sb_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +sbrec_port_binding_update_mirror_rules(struct northd_input
> >>> *input_data,
> >>> +                                       const struct ovn_port *op)
> >>> +{
> >>> +    size_t i = 0;
> >>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> >>> +        /* Needs deletion in SB */
> >>> +        struct shash nb_mirror_rules =
> >>> SHASH_INITIALIZER(&nb_mirror_rules);
> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +            shash_add(&nb_mirror_rules,
> >>> + op->nbsp->mirror_rules[i]->name,
> >>> + op->nbsp->mirror_rules[i]);
> >>> +        }
> >>> +
> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> >>> +            if (!shash_find(&nb_mirror_rules,
> >>> + op->sb->mirror_rules[i]->name)) {
> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> >>> + op->sb->mirror_rules[i]);
> >>> +            }
> >>> +        }
> >>> +
> >>> +        struct shash_node *node, *next;
> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> >>> +            shash_delete(&nb_mirror_rules, node);
> >>> +        }
> >>> +        shash_destroy(&nb_mirror_rules);
> >>> +
> >>> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
> >>> +        /* Needs addition in SB */
> >>> +        do_sb_mirror_addition(input_data, op);
> >>> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
> >>> +        /*
> >>> +         * Check if its the same mirrors on both SB and NB DBs
> >>> +         * If not update accordingly.
> >>> +         */
> >>> +        bool needs_sb_addition = false;
> >>> +        struct shash nb_mirror_rules =
> >>> SHASH_INITIALIZER(&nb_mirror_rules);
> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +            shash_add(&nb_mirror_rules,
> >>> + op->nbsp->mirror_rules[i]->name,
> >>> + op->nbsp->mirror_rules[i]);
> >>> +        }
> >>> +
> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> >>> +            if (!shash_find(&nb_mirror_rules,
> >>> + op->sb->mirror_rules[i]->name)) {
> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> >>> + op->sb->mirror_rules[i]);
> >>> +                    needs_sb_addition = true;
> >>> +            }
> >>> +        }
> >>> +
> >>> +        struct shash_node *node, *next;
> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> >>> +            shash_delete(&nb_mirror_rules, node);
> >>> +        }
> >>> +        shash_destroy(&nb_mirror_rules);
> >>> +
> >>> +        if (needs_sb_addition) {
> >>> +            do_sb_mirror_addition(input_data, op);
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>>   static void
> >>>   ovn_port_update_sbrec(struct northd_input *input_data,
> >>>                         struct ovsdb_idl_txn *ovnsb_txn,
> >>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input
> >>> *input_data,
> >>>           }
> >>>           sbrec_port_binding_set_external_ids(op->sb, &ids);
> >>>           smap_destroy(&ids);
> >>> +
> >>> +        if (!op->nbsp->n_mirror_rules) {
> >>> +            /* Nothing is set. Clear mirror_rules from pb. */
> >>> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> >>> +        } else {
> >>> +            /* Check if SB DB update needed */
> >>> + sbrec_port_binding_update_mirror_rules(input_data, op);
> >>> +        }
> >>> +
> >>>       }
> >>>       if (op->tunnel_key != op->sb->tunnel_key) {
> >>>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> >>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
> >>>       shash_destroy(&sb_meters);
> >>>   }
> >>>
> >>> +static bool
> >>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> >>> +                  const struct sbrec_mirror *sb_mirror)
> >>> +{
> >>> +
> >>> +    if (nb_mirror->index != sb_mirror->index) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    return false;
> >>> +}
> >>> +
> >>> +static void
> >>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> >>> +                             const char *mirror_name,
> >>> +                             const struct nbrec_mirror *nb_mirror,
> >>> +                             struct shash *sb_mirrors,
> >>> +                             struct sset *used_sb_mirrors)
> >>> +{
> >>> +    const struct sbrec_mirror *sb_mirror;
> >>> +    bool new_sb_mirror = false;
> >>> +
> >>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> >>> +    if (!sb_mirror) {
> >>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> >>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> >>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> >>> +        new_sb_mirror = true;
> >>> +    }
> >>> +    sset_add(used_sb_mirrors, mirror_name);
> >>> +
> >>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror,
> >>> sb_mirror)) {
> >>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> >>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> >>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> >>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +sync_mirrors(struct northd_input *input_data,
> >>> +            struct ovsdb_idl_txn *ovnsb_txn)
> >>> +{
> >>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> >>> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> >>> +
> >>> +    const struct sbrec_mirror *sb_mirror;
> >>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> >>> input_data->sbrec_mirror_table) {
> >>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> >>> +    }
> >>> +
> >>> +    const struct nbrec_mirror *nb_mirror;
> >>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
> >>> input_data->nbrec_mirror_table) {
> >>> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
> >>> nb_mirror,
> >>> +                                     &sb_mirrors, &used_sb_mirrors);
> >>> +    }
> >>> +
> >>> +    const char *used_mirror;
> >>> +    const char *used_mirror_next;
> >>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
> >>> &used_sb_mirrors) {
> >>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
> >>> +        sset_delete(&used_sb_mirrors,
> >>> SSET_NODE_FROM_NAME(used_mirror));
> >>> +    }
> >>> +    sset_destroy(&used_sb_mirrors);
> >>> +
> >>> +    struct shash_node *node, *next;
> >>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> >>> +        sbrec_mirror_delete(node->data);
> >>> +        shash_delete(&sb_mirrors, node);
> >>> +    }
> >>> +    shash_destroy(&sb_mirrors);
> >>> +}
> >>> +
> >>>   /*
> >>>    * struct 'dns_info' is used to sync the DNS records between OVN
> >>> Northbound db
> >>>    * and Southbound db.
> >>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
> >>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
> >>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
> >>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> >>> +    sync_mirrors(input_data, ovnsb_txn);
> >>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
> >>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
> >>>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> >>> diff --git a/northd/northd.h b/northd/northd.h
> >>> index da90e2815..17a62ea10 100644
> >>> --- a/northd/northd.h
> >>> +++ b/northd/northd.h
> >>> @@ -36,6 +36,7 @@ struct northd_input {
> >>>       const struct nbrec_acl_table *nbrec_acl_table;
> >>>       const struct nbrec_static_mac_binding_table
> >>>           *nbrec_static_mac_binding_table;
> >>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
> >>>
> >>>       /* Southbound table references */
> >>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
> >>> @@ -55,6 +56,7 @@ struct northd_input {
> >>>       const struct sbrec_chassis_private_table
> >>> *sbrec_chassis_private_table;
> >>>       const struct sbrec_static_mac_binding_table
> >>>           *sbrec_static_mac_binding_table;
> >>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
> >>>
> >>>       /* Indexes */
> >>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
> >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> >>> index 174364c8b..01de55222 100644
> >>> --- a/ovn-nb.ovsschema
> >>> +++ b/ovn-nb.ovsschema
> >>> @@ -1,7 +1,7 @@
> >>>   {
> >>>       "name": "OVN_Northbound",
> >>> -    "version": "6.3.0",
> >>> -    "cksum": "4042813038 31869",
> >>> +    "version": "6.4.0",
> >>> +    "cksum": "589874483 33352",
> >>>       "tables": {
> >>>           "NB_Global": {
> >>>               "columns": {
> >>> @@ -132,6 +132,11 @@
> >>>                                               "refType": "weak"},
> >>>                                    "min": 0,
> >>>                                    "max": 1}},
> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> >>> +                                          "refTable": "Mirror",
> >>> +                                          "refType": "weak"},
> >>> +                                  "min": 0,
> >>> +                                  "max": "unlimited"}},
> >>>                   "ha_chassis_group": {
> >>>                       "type": {"key": {"type": "uuid",
> >>>                                        "refTable": "HA_Chassis_Group",
> >>> @@ -301,6 +306,28 @@
> >>>                       "type": {"key": "string", "value": "string",
> >>>                                "min": 0, "max": "unlimited"}}},
> >>>               "isRoot": false},
> >>> +        "Mirror": {
> >>> +            "columns": {
> >>> +                "name": {"type": "string"},
> >>> +                "filter": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> ["from-lport",
> >>> + "to-lport",
> >>> + "both"]]}}},
> >>> +                "sink":{"type": "string"},
> >>> +                "type": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set", ["gre",
> >>> + "erspan"]]}}},
> >>> +                "index": {"type": "integer"},
> >>> +                "src": {"type": {"key": {"type": "uuid",
> >>> +                                           "refTable":
> >>> "Logical_Switch_Port",
> >>> +                                           "refType": "weak"},
> >>> +                                   "min": 0,
> >>> +                                   "max": "unlimited"}},
> >>> +                "external_ids": {
> >>> +                    "type": {"key": "string", "value": "string",
> >>> +                             "min": 0, "max": "unlimited"}}},
> >>> +            "indexes": [["name"]],
> >>> +            "isRoot": true},
> >>>           "Meter": {
> >>>               "columns": {
> >>>                   "name": {"type": "string"},
> >>> diff --git a/ovn-nb.xml b/ovn-nb.xml
> >>> index f41e9d7c0..d8730c8fc 100644
> >>> --- a/ovn-nb.xml
> >>> +++ b/ovn-nb.xml
> >>> @@ -1554,6 +1554,11 @@
> >>>         </column>
> >>>       </group>
> >>>
> >>> +    <column name="mirror_rules">
> >>> +        Mirror rules that apply to logical switch port which is the
> >>> source.
> >>> +        Please see the <ref table="Mirror"/> table.
> >>> +    </column>
> >>> +
> >>>       <column name="ha_chassis_group">
> >>>         References a row in the OVN Northbound database's
> >>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> >>> @@ -2491,6 +2496,64 @@
> >>>       </column>
> >>>     </table>
> >>>
> >>> +  <table name="Mirror" title="Mirror Entry">
> >>> +    <p>
> >>> +      Each row in this table represents one Mirror that can be used
> >>> for
> >>> +      port mirroring. These Mirrors are referenced by the
> >>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/>
> >>> column in
> >>> +      the <ref table="Logical_Switch_Port"/> table.
> >>> +    </p>
> >>> +
> >>> +    <column name="name">
> >>> +      <p>
> >>> +        Represents the name of the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="filter">
> >>> +      <p>
> >>> +        The value of this field represents selection criteria of
> >>> the mirror.
> >>> +        Supported values for filter to-lport / from-lport / both
> >>> +        to-lport - to mirror packets coming into logical port
> >>> +        from-lport - to mirror packets going out of logical port
> >>> +        both - to mirror packets coming into and going out of
> >>> logical port.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="sink">
> >>> +      <p>
> >>> +        The value of this field represents the destination/sink of
> >>> the mirror.
> >>> +        The value it takes is an IP address of the sink port.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="type">
> >>> +      <p>
> >>> +        The value of this field represents the type of the tunnel
> >>> used for
> >>> +        sending the mirrored packets. Supported Tunnel types gre
> >>> and erspan
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="index">
> >>> +      <p>
> >>> +        The value of this field represents the tunnel ID. Depending
> >>> on the
> >>> +        tunnel type configured, GRE key value if type GRE and
> >>> erspan_idx value
> >>> +        if ERSPAN
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="src">
> >>> +      <p>
> >>> +        The value of this field represents a list of source ports
> >>> for the
> >>> +        mirror. Please see the <ref table="Logical_Switch_Port"/>
> >>> table.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="external_ids">
> >>> +      See <em>External IDs</em> at the beginning of this document.
> >>> +    </column>
> >>> +  </table>
> >>> +
> >>>     <table name="Meter" title="Meter entry">
> >>>       <p>
> >>>         Each row in this table represents a meter that can be used
> >>> for QoS or
> >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> >>> index 576ebbdeb..b83134416 100644
> >>> --- a/ovn-sb.ovsschema
> >>> +++ b/ovn-sb.ovsschema
> >>> @@ -1,7 +1,7 @@
> >>>   {
> >>>       "name": "OVN_Southbound",
> >>> -    "version": "20.25.0",
> >>> -    "cksum": "53184112 28845",
> >>> +    "version": "20.26.0",
> >>> +    "cksum": "2344151793 30004",
> >>>       "tables": {
> >>>           "SB_Global": {
> >>>               "columns": {
> >>> @@ -142,6 +142,23 @@
> >>>               "indexes": [["datapath", "tunnel_key"],
> >>>                           ["datapath", "name"]],
> >>>               "isRoot": true},
> >>> +        "Mirror": {
> >>> +            "columns": {
> >>> +                "name": {"type": "string"},
> >>> +                "filter": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> + ["from-lport",
> >>> + "to-lport","both"]]}}},
> >>> +                "sink":{"type": "string"},
> >>> +                "type": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> +                                                     ["gre",
> >>> "erspan"]]}}},
> >>> +                "index": {"type": "integer"},
> >>> +                "external_ids": {
> >>> +                    "type": {"key": "string", "value": "string",
> >>> +                             "min": 0, "max": "unlimited"}}},
> >>> +            "indexes": [["name"]],
> >>> +            "isRoot": true},
> >>>           "Meter": {
> >>>               "columns": {
> >>>                   "name": {"type": "string"},
> >>> @@ -230,6 +247,11 @@
> >>> "refTable": "Encap",
> >>> "refType": "weak"},
> >>>                                       "min": 0, "max": "unlimited"}},
> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> >>> +                                          "refTable": "Mirror",
> >>> +                                          "refType": "weak"},
> >>> +                                  "min": 0,
> >>> +                                  "max": "unlimited"}},
> >>>                   "mac": {"type": {"key": "string",
> >>>                                    "min": 0,
> >>>                                    "max": "unlimited"}},
> >>> diff --git a/ovn-sb.xml b/ovn-sb.xml
> >>> index 315d60853..05c0db6b4 100644
> >>> --- a/ovn-sb.xml
> >>> +++ b/ovn-sb.xml
> >>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
> >>>       </column>
> >>>     </table>
> >>>
> >>> +  <table name="Mirror" title="Mirror Entry">
> >>> +    <p>
> >>> +      Each row in this table represents one Mirror that can be used
> >>> for
> >>> +      port mirroring. These Mirrors are referenced by the
> >>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
> >>> +      the <ref table="Port_Binding"/> table.
> >>> +    </p>
> >>> +
> >>> +    <column name="name">
> >>> +      <p>
> >>> +        Represents the name of the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="filter">
> >>> +      <p>
> >>> +        The value of this field represents selection criteria of
> >>> the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="sink">
> >>> +      <p>
> >>> +        The value of this field represents the destination/sink of
> >>> the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="type">
> >>> +      <p>
> >>> +        The value of this field represents the type of the tunnel
> >>> used for
> >>> +        sending the mirrored packets
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="index">
> >>> +      <p>
> >>> +        The value of this field represents the key/idx depending on
> >>> the
> >>> +        tunnel type configured
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="external_ids">
> >>> +      See <em>External IDs</em> at the beginning of this document.
> >>> +    </column>
> >>> +  </table>
> >>> +
> >>>     <table name="Meter" title="Meter entry">
> >>>       <p>
> >>>         Each row in this table represents a meter that can be used
> >>> for QoS or
> >>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
> >>>         </column>
> >>>       </group>
> >>>
> >>> +    <column name="mirror_rules">
> >>> +        Mirror rules that apply to the port binding.
> >>> +        Please see the <ref table="Mirror"/> table.
> >>> +    </column>
> >>> +
> >>>       <group title="Patch Options">
> >>>         <p>
> >>>           These options apply to logical ports with <ref
> >>> column="type"/> of
> >>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> >>> index 4d480e357..d79f9d929 100644
> >>> --- a/tests/ovn-nbctl.at
> >>> +++ b/tests/ovn-nbctl.at
> >>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
> >>>
> >>>   dnl
> >>> ---------------------------------------------------------------------
> >>>
> >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> >>> +AT_CHECK([ovn-nbctl ls-add sw0])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> >>> +
> >>> +dnl Add duplicate mirror name
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
> >>> 10.10.10.5], [1], [], [stderr])
> >>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach invalid source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [],
> >>> [stderr])
> >>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach source port to invalid mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [],
> >>> [stderr])
> >>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> >>> +
> >>> +dnl Attach one more source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> >>> +
> >>> +dnl Verify if multiple ports are attached to the same mirror properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +mirror3:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.3
> >>> +  Filter   :  to-lport
> >>> +  Index/Key:  2
> >>> +  Sources  :  sw0-port1  sw0-port3
> >>> +])
> >>> +
> >>> +dnl Detach one source port from mirror
> >>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> >>> +
> >>> +dnl Verify if detach source port from mirror happens properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +mirror3:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.3
> >>> +  Filter   :  to-lport
> >>> +  Index/Key:  2
> >>> +  Sources  :  sw0-port1
> >>> +])
> >>> +
> >>> +dnl Delete a single mirror which has source attached.
> >>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
> >>> +
> >>> +dnl Check if the detach happened from source properly
> >>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules
> >>> |  cut -b 3], [0], [dnl
> >>> +
> >>> +])
> >>> +
> >>> +dnl Check if the mirror deleted properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +])
> >>> +
> >>> +dnl Delete another mirror
> >>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
> >>> +
> >>> +dnl Update the Sink address
> >>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> >>> +
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  192.168.1.13
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +])
> >>> +
> >>> +dnl Delete all mirrors
> >>> +AT_CHECK([ovn-nbctl mirror-del])
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +])])
> >>> +
> >>> +dnl
> >>> ---------------------------------------------------------------------
> >>> +
> >>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
> >>>   AT_CHECK([ovn-nbctl lr-add lr0])
> >>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2],
> >>> [1], [],
> >>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> >>> index 4f399eccb..4e6c268e4 100644
> >>> --- a/tests/ovn-northd.at
> >>> +++ b/tests/ovn-northd.at
> >>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1}
> >>> meter_me__${acl2}
> >>>   AT_CLEANUP
> >>>   ])
> >>>
> >>> +OVN_FOR_EACH_NORTHD_NO_HV([
> >>> +AT_SETUP([Check NB-SB mirrors sync])
> >>> +AT_KEYWORDS([mirrors])
> >>> +ovn_start
> >>> +
> >>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"10.10.10.2"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +erspan
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . sink=192.168.1.13
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +erspan
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . type=gre
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . index=12
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +12
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . filter=to-lport
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +to-lport
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +12
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>>   OVN_FOR_EACH_NORTHD_NO_HV([
> >>>   AT_SETUP([ACL skip hints for stateless config])
> >>>   AT_KEYWORDS([acl])
> >>> diff --git a/tests/ovn.at b/tests/ovn.at
> >>> index f8b8db4df..cd5527ea1 100644
> >>> --- a/tests/ovn.at
> >>> +++ b/tests/ovn.at
> >>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
> >>>   AT_CLEANUP
> >>>   ])
> >>>
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror])
> >>> +AT_KEYWORDS([Mirror])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> >>> +    options:tx_pcap=hv1/vif1-tx.pcap \
> >>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> >>> +    ofport-request=1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> >>> +    options:tx_pcap=hv1/vif2-tx.pcap \
> >>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> >>> +    ofport-request=1
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +ovn-nbctl dump-flows > sbflows
> >>> +AT_CAPTURE_FILE([sbflows])
> >>> +
> >>> +for i in 1 2; do
> >>> +    : > vif$i.expected
> >>> +done
> >>> +
> >>> +net_add n2
> >>> +
> >>> +sim_add hv2
> >>> +as hv2
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:02:02:00\"
> >>> +ovn_attach n2 br-phys 192.168.1.12
> >>> +
> >>> +OVN_POPULATE_ARP
> >>> +
> >>> +as hv1
> >>> +
> >>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST
> >>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> >>> +#
> >>> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> >>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> >>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> >>> +# provided, then it should be the ip and icmp checksums of the packet
> >>> +# responded; otherwise, no reply is expected.
> >>> +# In the absence of an ip checksum calculation helpers, this relies
> >>> +# on the caller to provide the checksums for the ip and icmp headers.
> >>> +# XXX This should be more systematic.
> >>> +#
> >>> +# INPORT is an lport number, e.g. 11 for vif11.
> >>> +# ETH_SRC and ETH_DST are each 12 hex digits.
> >>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> >>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> >>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> >>> +# ENCAP_TYPE - gre or erspan
> >>> +# FILTER - Mirror Filter - to-lport / from-lport
> >>> +test_ipv4_icmp_request() {
> >>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5
> >>> ip_chksum=$6 icmp_chksum=$7
> >>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9
> >>> mirror_encap_type=${10} mirror_filter=${11}
> >>> +    shift; shift; shift; shift; shift; shift; shift
> >>> +    shift; shift; shift; shift;
> >>> +
> >>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> >>> +    local ip_ttl=02
> >>> +    local icmp_id=5fbf
> >>> +    local icmp_seq=0001
> >>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> >>> +    local icmp_type_code_request=0800
> >>> +    local
> >>>
> icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> >>> +    local
> >>>
> packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> >>> +
> >>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> >>> +
> >>> +    # Expect to receive the reply, if any. In same port where
> >>> packet was sent.
> >>> +    # Note: src and dst fields are expected to be reversed.
> >>> +    local icmp_type_code_response=0000
> >>> +    local reply_icmp_ttl=fe
> >>> +    local
> >>>
> reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> >>> +    local
> >>>
> reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> >>> +    echo $reply >> vif$inport.expected
> >>> +    local remote_mac=000000020200
> >>> +    local local_mac=000000010200
> >>> +    if test ${mirror_encap_type} = "gre" ; then
> >>> +        local
> >>> ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> >>> +        if test ${mirror_filter} = "to-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> >>> +        elif test ${mirror_filter} = "from-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> >>> +        fi
> >>> +    elif test ${mirror_encap_type} = "erspan" ; then
> >>> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> >>> +        local erspan_seq0=100088be000000001000000000000000
> >>> +        local erspan_seq1=100088be000000011000000000000000
> >>> +        if test ${mirror_filter} = "to-lport" ; then
> >>> +            local
> >>>
> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> >>>
> >>> +        elif test ${mirror_filter} = "from-lport" ; then
> >>> +            local
> >>>
> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> >>> +        fi
> >>> +    fi
> >>> +    echo $mirror >> br-phys_n1.expected
> >>> +
> >>> +}
> >>> +
> >>> +# Set IPs
> >>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> >>> +l1_ip=$(ip_to_hex 192 168 1 2)
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the reply
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . type=erspan
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the reply
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . filter=from-lport
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the request
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . type=gre
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the request
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +echo "---------OVN NB Mirror-----"
> >>> +ovn-nbctl mirror-list
> >>> +
> >>> +echo "---------OVS Mirror----"
> >>> +ovs-vsctl list Mirror
> >>> +
> >>> +echo "-----------------------"
> >>> +
> >>> +echo "Verifying Mirror deletion in OVS"
> >>> +# Set vif1 iface-id such that OVN releases port binding
> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> >>> +])
> >>> +
> >>> +# Set vif1 iface-id back to ls1-lp1
> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) =
> >>> "mirror0"])
> >>> +
> >>> +# Delete vif1 so that OVN releases port binding
> >>> +check ovs-vsctl del-port br-int vif1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> >>> +
> >>> +OVN_CLEANUP([hv1], [hv2])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk swap attachments])
> >>> +AT_KEYWORDS([Mirror test bulk swap attachments])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +# Equal detaches and attaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk attach multiple])
> >>> +AT_KEYWORDS([Mirror test bulk attach multiple])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-del
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +
> >>> +# Attaches multiple mirrors
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk more detach and less attach])
> >>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl --wait=hv sync
> >>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-del
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Detaches more than attaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk attach more than detach])
> >>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Attaches more than detaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk detach multiple])
> >>> +AT_KEYWORDS([Mirror test bulk detach multiple])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Detaches all
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>>
> >>>   OVN_FOR_EACH_NORTHD([
> >>>   AT_SETUP([Port Groups])
> >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> >>> index 811468dc6..af2e61435 100644
> >>> --- a/utilities/ovn-nbctl.c
> >>> +++ b/utilities/ovn-nbctl.c
> >>> @@ -271,6 +271,19 @@ QoS commands:\n\
> >>>                               remove QoS rules from SWITCH\n\
> >>>     qos-list SWITCH           print QoS rules for SWITCH\n\
> >>>   \n\
> >>> +Mirror commands:\n\
> >>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> >>> +                            add a mirror with given name\n\
> >>> +                            specify TYPE 'gre' or 'erspan'\n\
> >>> +                            specify the tunnel INDEX value\n\
> >>> +                                (indicates key if GRE\n\
> >>> +                                 erpsan_idx if ERSPAN)\n\
> >>> +                            specify FILTER for mirroring selection\n\
> >>> +                                'to-lport' / 'from-lport' / 'both'\n\
> >>> +                            specify Sink / Destination i.e. Remote
> >>> IP\n\
> >>> +  mirror-del [NAME]         remove mirrors\n\
> >>> +  mirror-list               print mirrors\n\
> >>> +\n\
> >>>   Meter commands:\n\
> >>>     [--fair]\n\
> >>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
> >>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
> >>>                               set dhcpv6 options for PORT\n\
> >>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
> >>>     lsp-get-ls PORT           get the logical switch which the port
> >>> belongs to\n\
> >>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
> >>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
> >>>   \n\
> >>>   Forwarding group commands:\n\
> >>>     [--liveness]\n\
> >>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_type);
> >>>   }
> >>>
> >>> +static void
> >>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> + &nbrec_logical_switch_port_col_mirror_rules);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static int
> >>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> >>> +{
> >>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> >>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> >>> +
> >>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> >>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> >>> +
> >>> +    return strcmp(mirror1->name,mirror2->name);
> >>> +}
> >>> +
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> >>> +                    bool must_exist,
> >>> +                    const struct nbrec_mirror **mirror_p)
> >>> +{
> >>> +    const struct nbrec_mirror *mirror = NULL;
> >>> +    *mirror_p = NULL;
> >>> +
> >>> +    struct uuid mirror_uuid;
> >>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> >>> +    if (is_uuid) {
> >>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> >>> +    }
> >>> +
> >>> +    if (!mirror) {
> >>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +            if (!strcmp(mirror->name, id)) {
> >>> +                break;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    if (!mirror && must_exist) {
> >>> +        return xasprintf("%s: mirror %s not found",
> >>> +                         id, is_uuid ? "UUID" : "name");
> >>> +    }
> >>> +
> >>> +    *mirror_p = mirror;
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *port = ctx->argv[1];
> >>> +    const char *mirror_name = ctx->argv[2];
> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +
> >>> +    char *error;
> >>> +
> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +
> >>> +    /*check if a mirror rule actually exists on that name or not*/
> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Check if same mirror rule already exists for the lsp */
> >>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> >>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> >>> +            bool may_exist = shash_find(&ctx->options,
> >>> "--may-exist") != NULL;
> >>> +            if (!may_exist) {
> >>> +                ctl_error(ctx, "Same mirror already existed on the
> >>> lsp %s.",
> >>> +                          ctx->argv[1]);
> >>> +                return;
> >>> +            }
> >>> +            return;
> >>> +        }
> >>> +    }
> >>> +
> >>> + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> >>> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *port = ctx->argv[1];
> >>> +    const char *mirror_name = ctx->argv[2];
> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +
> >>> +    char *error;
> >>> +
> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +
> >>> +    /*check if a mirror rule actually exists on that name or not*/
> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> >>> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> >>> +
> >>> +}
> >>> +
> >>>   static void
> >>>   nbctl_lsp_set_type(struct ctl_context *ctx)
> >>>   {
> >>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct
> >>> ctl_context *ctx)
> >>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
> >>>   }
> >>>
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +parse_filter(const char *arg, const char **selection_p)
> >>> +{
> >>> +    /* Validate selection.  Only require the first letter. */
> >>> +    if (arg[0] == 't') {
> >>> +        *selection_p = "to-lport";
> >>> +    } else if (arg[0] == 'f') {
> >>> +        *selection_p = "from-lport";
> >>> +    } else if (arg[0] == 'b') {
> >>> +        *selection_p = "both";
> >>> +    } else {
> >>> +        *selection_p = NULL;
> >>> +        return xasprintf("%s: selection must be \"to-lport\" or "
> >>> +                         "\"from-lport\" or \"both\" ", arg);
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +parse_type(const char *arg, const char **type_p)
> >>> +{
> >>> +    /* Validate type.  Only require the first letter. */
> >>> +    if (arg[0] == 'g') {
> >>> +        *type_p = "gre";
> >>> +    } else if (arg[0] == 'e') {
> >>> +        *type_p = "erspan";
> >>> +    } else {
> >>> +        *type_p = NULL;
> >>> +        return xasprintf("%s: type must be \"gre\" or "
> >>> +                         "\"erspan\"", arg);
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_add(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *filter = NULL;
> >>> +    const char *sink_ip = NULL;
> >>> +    const char *type = NULL;
> >>> +    const char *name = NULL;
> >>> +    char *new_sink_ip = NULL;
> >>> +    int64_t index;
> >>> +    char *error = NULL;
> >>> +    const struct nbrec_mirror *mirror_check = NULL;
> >>> +
> >>> +    /* Mirror Name */
> >>> +    name = ctx->argv[1];
> >>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> >>> +        if (!strcmp(mirror_check->name, name)) {
> >>> +            ctl_error(ctx, "Mirror with %s name already exists.",
> >>> +                      name);
> >>> +            return;
> >>> +        }
> >>> +    }
> >>> +
> >>> +    /* Tunnel Type - GRE/ERSPAN */
> >>> +    error = parse_type(ctx->argv[2], &type);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* tunnel index / GRE key / ERSPAN idx */
> >>> +    index = atoi(ctx->argv[3]);
> >> Shouldn't we validate the input is an actual number?
> >>
> >>> +
> >>> +    /* Filter for mirroring */
> >>> +    error = parse_filter(ctx->argv[4], &filter);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Destination / Sink details */
> >>> +    sink_ip = ctx->argv[5];
> >>> +
> >>> +    /* check if it is a valid ip */
> >>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
> >>> +    if (!new_sink_ip) {
> >>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
> >>> +    }
> >>> +
> >>> +    if (new_sink_ip) {
> >>> +        free(new_sink_ip);
> >>> +    } else {
> >>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Create the mirror. */
> >>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> >>> +    nbrec_mirror_set_name(mirror, name);
> >>> +    nbrec_mirror_set_index(mirror, index);
> >>> +    nbrec_mirror_set_filter(mirror, filter);
> >>> +    nbrec_mirror_set_type(mirror, type);
> >>> +    nbrec_mirror_set_sink(mirror, sink_ip);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_del(struct ctl_context *ctx)
> >>> +{
> >>> +    const struct nbrec_mirror *mirror, *next;
> >>> +
> >>> +    /* If a name is not specified, delete all mirrors. */
> >>> +    if (ctx->argc == 1) {
> >>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> >>> +            nbrec_mirror_delete(mirror);
> >>> +        }
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Remove the matching mirror. */
> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +        if (strcmp(ctx->argv[1], mirror->name)) {
> >>> +            continue;
> >>> +        }
> >>> +        nbrec_mirror_delete(mirror);
> >>> +        return;
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_list(struct ctl_context *ctx)
> >>> +{
> >>> +
> >>> +    const struct nbrec_mirror **mirrors = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +    size_t n_capacity = 0;
> >>> +    size_t n_mirrors = 0;
> >>> +
> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +        if (n_mirrors == n_capacity) {
> >>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof
> >>> *mirrors);
> >>> +        }
> >>> +
> >>> +        mirrors[n_mirrors] = mirror;
> >>> +        n_mirrors++;
> >>> +    }
> >>> +
> >>> +    if (n_mirrors) {
> >>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> >>> +    }
> >>> +
> >>> +    for (size_t i = 0; i < n_mirrors; i++) {
> >>> +        mirror = mirrors[i];
> >>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> >>> +        /* print all the values */
> >>> +        ds_put_format(&ctx->output, "  Type     : %s\n",
> >>> mirror->type);
> >>> +        ds_put_format(&ctx->output, "  Sink     : %s\n",
> >>> mirror->sink);
> >>> +        ds_put_format(&ctx->output, "  Filter   : %s\n",
> >>> mirror->filter);
> >>> +        ds_put_format(&ctx->output, "  Index/Key: %ld\n",
> >>> +                                                (long int)
> >>> mirror->index);
> >> You don't ned to cast if you pass %d formatter instead of %ld. The
> >> same applies to other places in the patch where you cast to long int.
> >> In general, casting is not needed and should be avoided.
> >>
> >>> + ds_put_cstr(&ctx->output,   "  Sources  :");
> >>> +        if (mirror->n_src > 0) {
> >>> +            struct svec srcs;
> >>> +            const char *src;
> >>> +            size_t j;
> >>> +            svec_init(&srcs);
> >>> +            for (j = 0; j < mirror->n_src; j++) {
> >>> +                svec_add(&srcs, mirror->src[j]->name);
> >>> +            }
> >>> +            svec_sort(&srcs);
> >>> +            SVEC_FOR_EACH (j, src, &srcs) {
> >>> +                ds_put_format(&ctx->output, "  %s", src);
> >>> +            }
> >>> +            svec_destroy(&srcs);
> >>> +        } else {
> >>> +            ds_put_cstr(&ctx->output, "  None attached");
> >>> +        }
> >>> +        ds_put_cstr(&ctx->output, "\n");
> >>> +    }
> >>> +
> >>> +    free(mirrors);
> >>> +}
> >>> +
> >>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
> >>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
> >>>       = {{&nbrec_logical_switch_port_col_name, NULL,
> >>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax
> >>> nbctl_commands[] = {
> >>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
> >>>         NULL, "", RO },
> >>>
> >>> +    /* mirror commands. */
> >>> +    { "mirror-add", 5, 5,
> >>> +      "NAME TYPE INDEX FILTER IP",
> >>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist",
> >>> RW },
> >>> +    { "mirror-del", 0, 1, "[NAME]",
> >>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> >>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list,
> >>> nbctl_mirror_list,
> >>> +      NULL, "", RO },
> >>> +
> >>>       /* meter commands. */
> >>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
> >>> nbctl_pre_meter_add,
> >>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> >>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax
> >>> nbctl_commands[] = {
> >>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
> >>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
> >>> nbctl_lsp_get_ls,
> >>>         NULL, "", RO },
> >>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> >>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> >>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> >>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
> >>>
> >>>       /* forwarding group commands. */
> >>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> >>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> >>> index f60dde1b6..3d73e9e25 100644
> >>> --- a/utilities/ovn-sbctl.c
> >>> +++ b/utilities/ovn-sbctl.c
> >>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_port_binding_col_mirror_rules);
> >>>
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_logical_flow_col_logical_datapath);
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_logical_flow_col_logical_dp_group);
> >>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class
> >>> tables[SBREC_N_TABLES] = {
> >>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
> >>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
> >>>
> >>> +    [SBREC_TABLE_MIRROR].row_ids[0]
> >>> +    = {&sbrec_mirror_col_name, NULL, NULL},
> >>> +
> >>>       [SBREC_TABLE_METER].row_ids[0]
> >>>       = {&sbrec_meter_col_name, NULL, NULL},
> >>>
> >>> --
> >>> 2.31.1
> >>>
>
>
Ihar Hrachyshka Nov. 16, 2022, 1:31 p.m. UTC | #5
On 11/16/22 8:08 AM, Abhiram R N wrote:
> Hi Ihar,
>
> Thanks for your inputs. I think I have found the issue in the test.
> In the test case I had missed one thing. Below are the details
>
> The log which you shared as the last message in ovs-vswitchd log led 
> me to it.
>
> > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max > 
> translation depth 64 on bridge br-int while processing > 
> arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>
> What I had missed is, the creation of hv2, setting ip to br-phys and 
> ARP resolving. (See code at the end for what I have added).
> Once I added this now everything is passing fine. I don't see any 
> failures.
>
> Also everything works in a single test case. So, I will revert back to 
> the old method of having 1 bulk updates test case and verify all cases 
> there and with below code, will submit a patch soon.
>
> Since in the bulk updates test case we were not verifying the actual 
> packet flow (already covered in a separate test) I had thought the 
> below code was not needed.
> But that wasn't the case!
>
> FYI, what I added now...
>
> net_add n2
>
> sim_add hv2
> as hv2
> ovs-vsctl add-br br-phys -- set bridge br-phys 
> other-config:hwaddr=\"00:00:00:02:02:00\"
> ovn_attach n2 br-phys 192.168.1.12
>
> OVN_POPULATE_ARP


While this change to the test case may fix the test failure, I don't 
think we should just ignore the fact that any test case creating a 
number of mirrors in ovsdb may put vswitchd into a 100% cpu spin loop 
that effectively freezes all processing. We'll need to understand what 
happens to vswitchd with the original test case.


>
> Thanks & Regards,
> Abhiram R N
>
> On Wed, Nov 16, 2022 at 4:39 AM Ihar Hrachyshka <ihrachys@redhat.com> 
> wrote:
>
>     On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
>     > I think there's a problem with the bulk tests added in this
>     patch. I
>     > will cover this issue in this email, and I'll send my code review
>     > tomorrow as promised, since it's getting late here.
>     >
>     >
>     > When running the whole suite locally, I get the following failures:
>     >
>     >
>     > 401: Mirror test bulk swap attachments -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425
>     <http://ovn.at:16425>)
>     > 402: Mirror test bulk swap attachments -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425
>     <http://ovn.at:16425>)
>     > 403: Mirror test bulk attach multiple -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537
>     <http://ovn.at:16537>)
>     > 410: Mirror test bulk more detach and less attach -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=no ok
>     > 412: Mirror test bulk attach more than detach -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=no ok
>     > 416: Mirror test bulk detach multiple -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=no ok
>     > 408: Mirror test bulk more detach and less attach -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650
>     <http://ovn.at:16650>)
>     > 417: Mirror test bulk detach multiple -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=yes ok
>     > 409: Mirror test bulk more detach and less attach -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650
>     <http://ovn.at:16650>)
>     > 411: Mirror test bulk attach more than detach -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766
>     <http://ovn.at:16766>)
>     > 418: Mirror test bulk detach multiple -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=no ok
>     > 413: Mirror test bulk attach more than detach -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766
>     <http://ovn.at:16766>)
>     > 415: Mirror test bulk detach multiple -- ovn-northd --
>     > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879
>     <http://ovn.at:16879>)
>     > 414: Mirror test bulk attach more than detach -- ovn-northd --
>     > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766
>     <http://ovn.at:16766>)
>     >
>     >
>     > Note that some of the test case variants passed, and I don't think
>     > there's a clear pattern as to which of variants in the test
>     matrix do
>     > fail.
>     >
>     >
>     > The error that triggers the failure is during ovs-vswitchd cleanup:
>     >
>     >
>     > ./ovn.at:16425 <http://ovn.at:16425>: ovs-appctl --timeout=10 -t
>     ovs-vswitchd exit --cleanup
>     > --- /dev/null   2022-11-04 04:09:25.869645998 +0000
>     > +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr
>     > 2022-11-15 16:31:15.557479369 +0000
>     > @@ -0,0 +1,2 @@
>     > +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with
>     signal
>     > 14 (Alarm clock)
>     >
>     +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source:
>     line
>     > 282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t
>     > ovs-vswitchd exit --cleanup
>     > ./ovn.at:16425 <http://ovn.at:16425>: exit code was 142, expected 0
>     >
>     >
>     > The very last message in ovs-vswitchd log on hv1 is exactly 10
>     seconds
>     > before the alarm clock error:
>     >
>     >
>     > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max
>     > translation depth 64 on bridge br-int while processing
>     >
>     arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>     >
>     >
>     > I don't see coredumps generated for any of test processes, so it's
>     > probably not the case of ovs-vswitchd crashing on exit request.
>     >
>     >
>     > I tried to adjust your test cases to a minimal reproducer and I
>     found
>     > that if a test case creates two mirrors, both of to-lport type,
>     then
>     > ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl
>     > requests, nor it handles new ports. But if I merely change the
>     type of
>     > one of mirrors in the test to from-lport, the test passes.
>     >
>     >
>     > On the other hand, a consistent way to trigger the failure is
>     adding a
>     > 'sleep 3' at the end of a test case just before cleanup,
>     apparently to
>     > allow vswitchd to catch on the mirror updates and lock somewhere in
>     > the code. I see vswitchd spinning at ~100% cpu in 'top' output
>     when it
>     > gets into this state. It's clearly doing SOMETHING, not just
>     sleeping. :)
>     >
>     >
>     > I suspect there's some bug inside vswitchd that makes it lock /
>     spin
>     > for a particular setup of mirrors. Whatever OVN sets up in vswitchd
>     > database, the latter should not freeze. It would be helpful to
>     provide
>     > a short ovs-only reproducer for the situation that would not
>     involve
>     > OVN so that our OVS friends can take a look.
>     >
>     >
>     > For the record, the mirrors in ovsdb are:
>     >
>     >
>     > _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
>     > external_ids        : {}
>     > name                : mirror0
>     > output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
>     > output_vlan         : []
>     > select_all          : false
>     > select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab,
>     > 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
>     > select_src_port     : []
>     > select_vlan         : []
>     > snaplen             : []
>     > statistics          : {}
>     >
>     > _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
>     > external_ids        : {}
>     > name                : mirror1
>     > output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
>     > output_vlan         : []
>     > select_all          : false
>     > select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6,
>     > 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
>     > select_src_port     : []
>     > select_vlan         : []
>     > snaplen             : []
>     > statistics          : {}
>     >
>     >
>     > Bridge output here:
>     >
>     >
>     > 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
>     >     Bridge br-int
>     >         fail_mode: secure
>     >         datapath_type: system
>     >         Port vif4
>     >             Interface vif4
>     >         Port vif2
>     >             Interface vif2
>     >         Port br-int
>     >             Interface br-int
>     >                 type: internal
>     >         Port vif1
>     >             Interface vif1
>     >         Port ovn-mirror0
>     >             Interface ovn-mirror0
>     >                 type: gre
>     >                 options: {key="0", remote_ip="192.168.1.12"}
>     >         Port vif3
>     >             Interface vif3
>     >         Port ovn-mirror1
>     >             Interface ovn-mirror1
>     >                 type: gre
>     >                 options: {key="1", remote_ip="192.168.1.12"}
>     >         Port patch-br-int-to-ln-public
>     >             Interface patch-br-int-to-ln-public
>     >                 type: patch
>     >                 options: {peer=patch-ln-public-to-br-int}
>     >     Bridge br-phys
>     >         Port br-phys
>     >             Interface br-phys
>     >                 type: internal
>     >                 options:
>     >
>     {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap",
>
>     >
>     tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
>     >         Port br-phys_n1
>     >             Interface br-phys_n1
>     >                 options:
>     >
>     {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap",
>
>     >
>     stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock",
>
>     >
>     tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
>
>     >
>     >         Port patch-ln-public-to-br-int
>     >             Interface patch-ln-public-to-br-int
>     >                 type: patch
>     >                 options: {peer=patch-br-int-to-ln-public}
>     >
>     Perhaps this may be of relevance: when I attach gdb to vswitchd
>     process,
>     I can see a never-ending stack of calls in thread 1 that looks like:
>
>     #0  0x0000000000406760 in memcpy@plt ()
>     #1  0x0000000000499d88 in dp_packet_clone_with_headroom
>     (buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
>     #2  0x000000000049bd4d in dp_packet_batch_clone (dst=0x7fffcbf54740,
>     src=0x7fffcbf55230) at lib/dp-packet.h:863
>     #3  0x00000000004b15ef in dp_execute_output_action
>     (pmd=0x7f638115f010,
>     packets_=0x7fffcbf55230, should_steal=false, port_no=3) at
>     lib/dpif-netdev.c:8696
>     #4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200,
>     packets_=0x7fffcbf55230, a=0x7fffcbf555c0, should_steal=false) at
>     lib/dpif-netdev.c:8787
>     #5  0x0000000000507834 in odp_execute_actions (dp=0x7fffcbf55200,
>     batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578,
>     actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
>          at lib/odp-execute.c:993
>     #6  0x00000000004b251e in dp_netdev_execute_actions
>     (pmd=0x7f638115f010,
>     packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60,
>     actions=0x7fffcbf55578, actions_len=88)
>          at lib/dpif-netdev.c:9105
>     #7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630,
>     execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
>     #8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630,
>     ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>     lib/dpif-netdev.c:4606
>     #9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630,
>     ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>     lib/dpif.c:1372
>     #10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630,
>     execute=0x7fffcbf55500) at lib/dpif.c:1326
>     #11 0x000000000043e46d in ofproto_dpif_execute_actions__
>     (ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0,
>     ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
>          packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
>     #12 0x0000000000467e8a in compose_table_xlate (ctx=0x7fffcbf5f240,
>     out_dev=0x1879430, packet=0x7fffcbf56050) at
>     ofproto/ofproto-dpif-xlate.c:3526
>     #13 0x0000000000468020 in tnl_send_arp_request (ctx=0x7fffcbf5f240,
>     out_dev=0x1879430, eth_src=..., ip_src=184658112,
>     ip_dst=201435328) at
>     ofproto/ofproto-dpif-xlate.c:3555
>     #14 0x0000000000468710 in native_tunnel_output (ctx=0x7fffcbf5f240,
>     xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7,
>     truncate=false,
>     is_last_action=false)
>          at ofproto/ofproto-dpif-xlate.c:3721
>     #15 0x000000000046a78e in compose_output_action__
>     (ctx=0x7fffcbf5f240,
>     ofp_port=7, xr=0x0, check_stp=true, is_last_action=false,
>     truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
>     #16 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
>     ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at
>     ofproto/ofproto-dpif-xlate.c:4416
>     #17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240,
>     out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at
>     ofproto/ofproto-dpif-xlate.c:2533
>     #18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240,
>     xbundle=0x1880d00, mirrors=2) at ofproto/ofproto-dpif-xlate.c:2190
>     #19 0x000000000046a96b in compose_output_action__
>     (ctx=0x7fffcbf5f240,
>     ofp_port=1, xr=0x0, check_stp=true, is_last_action=false,
>     truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
>     #20 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
>     ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at
>     ofproto/ofproto-dpif-xlate.c:4416
>     #21 0x000000000046cf3e in xlate_output_action (ctx=0x7fffcbf5f240,
>     port=1, controller_len=0, may_packet_in=true, is_last_action=false,
>     truncate=false, group_bucket_action=false)
>          at ofproto/ofproto-dpif-xlate.c:5361
>     #22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638,
>     ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>     group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
>     #23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>     rule=0x1860480, deepens=false, is_last_action=false,
>     actions_xlator=0x470caf <do_xlate_actions>)
>          at ofproto/ofproto-dpif-xlate.c:4439
>     #24 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
>     in_port=5, table_id=65 'A', may_packet_in=false,
>     honor_table_miss=false,
>     with_ct_orig=false, is_last_action=false,
>          xlator=0x470caf <do_xlate_actions>) at
>     ofproto/ofproto-dpif-xlate.c:4568
>     #25 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
>     resubmit=0x185cfb8, is_last_action=false) at
>     ofproto/ofproto-dpif-xlate.c:4879
>     #26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8,
>     ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>     group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>     #27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>     rule=0x185ce00, deepens=false, is_last_action=false,
>     actions_xlator=0x470caf <do_xlate_actions>)
>          at ofproto/ofproto-dpif-xlate.c:4439
>     #28 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
>     in_port=5, table_id=64 '@', may_packet_in=false,
>     honor_table_miss=false,
>     with_ct_orig=false, is_last_action=false,
>          xlator=0x470caf <do_xlate_actions>) at
>     ofproto/ofproto-dpif-xlate.c:4568
>     #29 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
>     resubmit=0x1834458, is_last_action=false) at
>     ofproto/ofproto-dpif-xlate.c:4879
>     #30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458,
>     ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>     group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>
>     it goes like that over and over and over for hundreds if not
>     thousands
>     of calls down the stack until
>
>     #5738 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
>     in_port=65533, table_id=9 '\t', may_packet_in=false,
>     honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>     xlator=0x470caf <do_xlate_actions>) at
>     ofproto/ofproto-dpif-xlate.c:4568
>     #5739 0x000000000046bbd3 in xlate_ofpact_resubmit
>     (ctx=0x7fffcc09b330,
>     resubmit=0x1831ac0, is_last_action=true) at
>     ofproto/ofproto-dpif-xlate.c:4879
>     #5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68,
>     ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true,
>     group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>     #5741 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcc09b330,
>     rule=0x18318b0, deepens=false, is_last_action=true,
>     actions_xlator=0x470caf <do_xlate_actions>) at
>     ofproto/ofproto-dpif-xlate.c:4439
>     #5742 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
>     in_port=65533, table_id=8 '\b', may_packet_in=false,
>     honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>     xlator=0x470caf <do_xlate_actions>) at
>     ofproto/ofproto-dpif-xlate.c:4568
>     #5743 0x000000000046bbd3 in xlate_ofpact_resubmit
>     (ctx=0x7fffcc09b330,
>     resubmit=0x187bcc0, is_last_action=true) at
>     ofproto/ofproto-dpif-xlate.c:4879
>     #5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80,
>     ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true,
>     group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>     #5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0,
>     xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
>     #5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0,
>     opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
>     #5747 0x00000000004250d7 in ofproto_packet_out_start
>     (ofproto=0x17c10e0,
>     opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
>     #5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40,
>     oh=0x17fdfa0) at ofproto/ofproto.c:3764
>     #5749 0x000000000042fce2 in handle_single_part_openflow
>     (ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at
>     ofproto/ofproto.c:8664
>     #5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40,
>     msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
>     #5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40,
>     handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:1329
>     #5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0,
>     handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:356
>     #5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at
>     ofproto/ofproto.c:1933
>     #5754 0x00000000004101d4 in bridge_run__ () at vswitchd/bridge.c:3210
>     #5755 0x00000000004103d3 in bridge_run () at vswitchd/bridge.c:3269
>     #5756 0x0000000000415cf0 in main (argc=10, argv=0x7fffcc09dff8) at
>     vswitchd/ovs-vswitchd.c:129
>
>     The main thread never getting out of some processing code to reach
>     any
>     other handlers (e.g. for appctl requests?)
>
>     I'm adding Ilya to CC in case he has an idea why vswitchd could
>     lock /
>     freeze / spin indefinitely on two to-lport mirror creation.
>
>
>     > On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
>     >> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N
>     <abhiramrn@gmail.com> wrote:
>     >>> Mirror creation just creates the mirror. The lsp-attach-mirror
>     >>> triggers the sequence to create Mirror in OVS DB on compute node.
>     >>> OVS already supports Port Mirroring.
>     >>>
>     >>> Note: This is targeted to mirror to destinations anywhere
>     outside the
>     >>> cluster where the analyser resides and it need not be an OVN node.
>     >>>
>     >>> Example commands are as below:
>     >>>
>     >>> Mirror creation
>     >>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>     >>>
>     >>> Attach a logical port to the mirror.
>     >>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>     >>>
>     >>> Detach a source from Mirror
>     >>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>     >>>
>     >>> Mirror deletion
>     >>> ovn-nbctl mirror-del mirror1
>     >>>
>     >>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>     >>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>     >>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
>     >>> ---
>     >>> v12 --> V13: Made each of bulk test cases(in ovn.at
>     <http://ovn.at>) as separate
>     >>>               test to make it pass consistently.
>     >>> V11 --> V12: Minor fix in ovn.at <http://ovn.at> to solve
>     intermittent failures
>     >>>
>     >>> V10 --> V11: Addressed review comments from V10 by Ihar
>     >>>             i) Expanded bulk updates test cases in ovn.at
>     <http://ovn.at>
>     >>>                Overall below cases are covered
>     >>>                a) Attaches multiple mirrors (new)
>     >>>                b) Equal detaches and attaches (same as V10)
>     >>>                c) Detaches more than attaches (new)
>     >>>                d) Attaches more than detaches (new)
>     >>>                e) Detaches all (new)
>     >>>            ii) Addressed the detach all case in mirror.c
>     >>>           iii) Minor correction in NEWS
>     >>>            iv) Added invalid mirror attach case in
>     ovn-nbctl.at <http://ovn-nbctl.at>
>     >>>
>     >>> Files modified (V10 --> V11):
>     >>> Code --> mirror.c
>     >>> Test --> ovn.at <http://ovn.at>, ovn-nbctl.at
>     <http://ovn-nbctl.at>
>     >>> Misc --> NEWS
>     >>>
>     >>> Ihar,
>     >>>      Regarding mirror_delete function param delete_all it is
>     wrt the
>     >>> port binding and if a port binding is removed we delete all its
>     >>> attachment. Already that use case is covered in ovn.at
>     <http://ovn.at>.
>     >>> Having said that the detaches all had issue in mirror_delete which
>     >>> I have addressed. With all the above cases added now in bulk
>     updates
>     >>> hope it should give good assurance.
>     >>>
>     >>>   NEWS                        |   1 +
>     >>>   controller/automake.mk <http://automake.mk> |   4 +-
>     >>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>     >>>   controller/mirror.h         |  53 +++
>     >>>   controller/ovn-controller.c | 266 ++++++++++--
>     >>>   northd/en-northd.c          |   4 +
>     >>>   northd/inc-proc-northd.c    |   4 +
>     >>>   northd/northd.c             | 172 ++++++++
>     >>>   northd/northd.h             |   2 +
>     >>>   ovn-nb.ovsschema            |  31 +-
>     >>>   ovn-nb.xml                  |  63 +++
>     >>>   ovn-sb.ovsschema            |  26 +-
>     >>>   ovn-sb.xml                  |  50 +++
>     >>>   tests/ovn-nbctl.at <http://ovn-nbctl.at> | 120 ++++++
>     >>>   tests/ovn-northd.at <http://ovn-northd.at> | 102 +++++
>     >>>   tests/ovn.at <http://ovn.at> | 778
>     >>> ++++++++++++++++++++++++++++++++++++
>     >>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>     >>>   utilities/ovn-sbctl.c       |   4 +
>     >>>   18 files changed, 2547 insertions(+), 28 deletions(-)
>     >>>   create mode 100644 controller/mirror.c
>     >>>   create mode 100644 controller/mirror.h
>     >>>
>     >>> diff --git a/NEWS b/NEWS
>     >>> index 224a7b83e..84b22abdb 100644
>     >>> --- a/NEWS
>     >>> +++ b/NEWS
>     >>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>     >>>       any of LR's LRP IP, there is no need to create SNAT entry.
>     >>> Now such
>     >>>       traffic destined to LRP IP is not dropped.
>     >>>     - Bump python version required for building OVN to 3.6.
>     >>> +  - Added Support for Remote Port Mirroring.
>     >>>
>     >>>   OVN v22.06.0 - 03 Jun 2022
>     >>>   --------------------------
>     >>> diff --git a/controller/automake.mk <http://automake.mk>
>     b/controller/automake.mk <http://automake.mk>
>     >>> index c2ab1bbe6..334672b4d 100644
>     >>> --- a/controller/automake.mk <http://automake.mk>
>     >>> +++ b/controller/automake.mk <http://automake.mk>
>     >>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>     >>>          controller/ovsport.h \
>     >>>          controller/ovsport.c \
>     >>>          controller/vif-plug.h \
>     >>> -       controller/vif-plug.c
>     >>> +       controller/vif-plug.c \
>     >>> +       controller/mirror.h \
>     >>> +       controller/mirror.c
>     >>>
>     >>>   controller_ovn_controller_LDADD = lib/libovn.la
>     <http://libovn.la>
>     >>> $(OVS_LIBDIR)/libopenvswitch.la <http://libopenvswitch.la>
>     >>>   man_MANS += controller/ovn-controller.8
>     >>> diff --git a/controller/mirror.c b/controller/mirror.c
>     >>> new file mode 100644
>     >>> index 000000000..11f2b63a6
>     >>> --- /dev/null
>     >>> +++ b/controller/mirror.c
>     >>> @@ -0,0 +1,538 @@
>     >>> +/* Copyright (c) 2022 Red Hat, Inc.
>     >>> + *
>     >>> + * Licensed under the Apache License, Version 2.0 (the
>     "License");
>     >>> + * you may not use this file except in compliance with the
>     License.
>     >>> + * You may obtain a copy of the License at:
>     >>> + *
>     >>> + * http://www.apache.org/licenses/LICENSE-2.0
>     >>> + *
>     >>> + * Unless required by applicable law or agreed to in writing,
>     software
>     >>> + * distributed under the License is distributed on an "AS IS"
>     BASIS,
>     >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>     express or
>     >>> implied.
>     >>> + * See the License for the specific language governing
>     permissions and
>     >>> + * limitations under the License.
>     >>> + */
>     >>> +
>     >>> +#include <config.h>
>     >>> +#include <unistd.h>
>     >>> +
>     >>> +/* library headers */
>     >>> +#include "lib/sset.h"
>     >>> +#include "lib/util.h"
>     >>> +
>     >>> +/* OVS includes. */
>     >>> +#include "lib/vswitch-idl.h"
>     >>> +#include "openvswitch/vlog.h"
>     >>> +
>     >>> +/* OVN includes. */
>     >>> +#include "binding.h"
>     >>> +#include "lib/ovn-sb-idl.h"
>     >>> +#include "mirror.h"
>     >>> +
>     >>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
>     >>> +
>     >>> +/* Static function declarations */
>     >>> +
>     >>> +static const struct ovsrec_port *
>     >>> +get_port_for_iface(const struct ovsrec_interface *iface,
>     >>> +                  const struct ovsrec_bridge *br_int)
>     >>> +{
>     >>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
>     >>> +        const struct ovsrec_port *p = br_int->ports[i];
>     >>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
>     >>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
>     >>> +                return p;
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +    return NULL;
>     >>> +}
>     >>> +
>     >>> +static bool
>     >>> +mirror_create(const struct sbrec_port_binding *pb,
>     >>> +              struct port_mirror_ctx *pm_ctx)
>     >>> +{
>     >>> +    const struct ovsrec_mirror *mirror = NULL;
>     >>> +
>     >>> +    if (pb->n_up && !pb->up[0]) {
>     >>> +        return true;
>     >>> +    }
>     >>> +
>     >>> +    if (pb->chassis != pm_ctx->chassis_rec) {
>     >>> +        return true;
>     >>> +    }
>     >>> +
>     >>> +    if (!pm_ctx->ovs_idl_txn) {
>     >>> +        return false;
>     >>> +    }
>     >>> +
>     >>> +
>     >>> +    VLOG_INFO("Mirror rule(s) present for %s ",
>     pb->logical_port);
>     >>> +    /* Loop through the mirror rules */
>     >>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>     >>> +        /* check if the mirror already exists in OVS DB */
>     >>> +        bool create_mirror = true;
>     >>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror,
>     pm_ctx->mirror_table) {
>     >>> +            if (!strcmp(pb->mirror_rules[i]->name,
>     mirror->name)) {
>     >>> +                /* Mirror with same name already exists
>     >>> +                 * No need to create mirror
>     >>> +                 */
>     >>> +                create_mirror = false;
>     >>> +                break;
>     >>> +            }
>     >>> +        }
>     >>> +
>     >>> +        if (create_mirror) {
>     >>> +
>     >>> +            struct smap options = SMAP_INITIALIZER(&options);
>     >>> +            char *port_name, *key;
>     >>> +
>     >>> +            key = xasprintf("%ld",(long int)
>     >>> pb->mirror_rules[i]->index);
>     >>> +            smap_add(&options, "remote_ip",
>     >>> pb->mirror_rules[i]->sink);
>     >>> +            smap_add(&options, "key", key);
>     >>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>     >>> +                /* Set the ERSPAN index */
>     >>> +                smap_add(&options, "erspan_idx", key);
>     >>> +                smap_add(&options, "erspan_ver","1");
>     >>> +
>     >>> +            }
>     >>> +            struct ovsrec_interface *iface =
>     >>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>     >>> +            port_name = xasprintf("ovn-%s",
>     >>> + pb->mirror_rules[i]->name);
>     >>> +
>     >>> +            ovsrec_interface_set_name(iface, port_name);
>     >>> +            ovsrec_interface_set_type(iface,
>     >>> pb->mirror_rules[i]->type);
>     >>> + ovsrec_interface_set_options(iface, &options);
>     >>> +
>     >>> +            struct ovsrec_port *port =
>     >>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>     >>> +            ovsrec_port_set_name(port, port_name);
>     >>> +            ovsrec_port_set_interfaces(port, &iface, 1);
>     >>> +
>     >>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>     >>> +
>     >>> +            smap_destroy(&options);
>     >>> +            free(port_name);
>     >>> +            free(key);
>     >>> +
>     >>> +            VLOG_INFO("Creating Mirror in OVS DB");
>     >>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>     >>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>     >>> + ovsrec_mirror_update_output_port_addvalue(mirror, port);
>     >>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>     >>> + mirror);
>     >>> +        }
>     >>> +
>     >>> +        struct local_binding *lbinding = local_binding_find(
>     >>> + pm_ctx->local_bindings,
>     >>> pb->logical_port);
>     >>> +        const struct ovsrec_port *p =
>     >>> + get_port_for_iface(lbinding->iface,
>     >>> pm_ctx->br_int);
>     >>> +        if (p) {
>     >>> +            if
>     (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>     >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>     >>> +            } else if
>     >>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>     >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>     >>> +            } else {
>     >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>     >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +    return true;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +check_and_update_mirror_table(const struct sbrec_mirror
>     *sb_mirror,
>     >>> +                              struct ovsrec_mirror *ovs_mirror)
>     >>> +{
>     >>> +    char *filter;
>     >>> +    if ((ovs_mirror->n_select_dst_port)
>     >>> +            && (ovs_mirror->n_select_src_port)) {
>     >>> +        filter = "both";
>     >>> +    } else if (ovs_mirror->n_select_dst_port) {
>     >>> +        filter = "to-lport";
>     >>> +    } else {
>     >>> +        filter = "from-lport";
>     >>> +    }
>     >>> +
>     >>> +    if (strcmp(sb_mirror->filter, filter)) {
>     >>> +        if (!strcmp(sb_mirror->filter,"from-lport")
>     >>> +                              && !strcmp(filter,"both")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_dst_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>     >>> + ovs_mirror->select_dst_port[i]);
>     >>> +            }
>     >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>     >>> +                              && !strcmp(filter,"both")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_src_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>     >>> + ovs_mirror->select_src_port[i]);
>     >>> +            }
>     >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>     >>> +                              && !strcmp(filter,"from-lport")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_src_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>     >>> + ovs_mirror->select_src_port[i]);
>     >>> +            }
>     >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>     >>> +                              && !strcmp(filter,"to-lport")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_dst_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>     >>> + ovs_mirror->select_dst_port[i]);
>     >>> +            }
>     >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>     >>> +                              && !strcmp(filter,"from-lport")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_src_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>     >>> + ovs_mirror->select_src_port[i]);
>     >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>     >>> + ovs_mirror->select_src_port[i]);
>     >>> +            }
>     >>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>     >>> +                              && !strcmp(filter,"to-lport")) {
>     >>> +            for (size_t i = 0; i <
>     ovs_mirror->n_select_dst_port;
>     >>> i++) {
>     >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>     >>> + ovs_mirror->select_dst_port[i]);
>     >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>     >>> + ovs_mirror->select_dst_port[i]);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +check_and_update_interface_table(const struct sbrec_mirror
>     *sb_mirror,
>     >>> +                                   struct ovsrec_mirror
>     *ovs_mirror)
>     >>> +{
>     >>> +    struct smap options = SMAP_INITIALIZER(&options);
>     >>> +    char *key, *type;
>     >>> +    struct ovsrec_interface *iface =
>     >>> + ovs_mirror->output_port->interfaces[0];
>     >>> +    struct smap *opts = &iface->options;
>     >>> +
>     >>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>     >>> +    if (erspan_ver) {
>     >>> +        type = "erspan";
>     >>> +    } else {
>     >>> +        type = "gre";
>     >>> +    }
>     >>> +    if (strcmp(type, sb_mirror->type)) {
>     >>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
>     >>> +    }
>     >>> +
>     >>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>     >>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
>     >>> +    smap_add(&options, "key", key);
>     >>> +
>     >>> +    if (!strcmp(sb_mirror->type, "erspan")) {
>     >>> +        /* Set the ERSPAN index */
>     >>> +        smap_add(&options, "erspan_idx", key);
>     >>> +        smap_add(&options, "erspan_ver","1");
>     >>> +    }
>     >>> +
>     >>> +    ovsrec_interface_set_options(iface, &options);
>     >>> +    smap_destroy(&options);
>     >>> +    free(key);
>     >>> +
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +mirror_update(const struct sbrec_mirror *sb_mirror,
>     >>> +              struct ovsrec_mirror *ovs_mirror)
>     >>> +{
>     >>> + check_and_update_interface_table(sb_mirror, ovs_mirror);
>     >>> +
>     >>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>     >>> +}
>     >>> +
>     >>> +static bool
>     >>> +mirror_delete(const struct sbrec_port_binding *pb,
>     >>> +              struct port_mirror_ctx *pm_ctx,
>     >>> +              struct shash *pb_mirror_map,
>     >>> +              bool delete_all)
>     >>> +{
>     >>> +
>     >>> +    if (!pm_ctx->ovs_idl_txn) {
>     >>> +        return false;
>     >>> +    }
>     >>> +
>     >>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>     >>> +
>     >>> +    if (!delete_all) {
>     >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>     >>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
>     >>> pb->n_mirror_rules) {
>     >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>     >>> +
>     >>> +            struct ovsrec_mirror *ovs_mirror = NULL;
>     >>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>     >>> + pb->mirror_rules[i]->name);
>     >>> +            if (ovs_mirror) {
>     >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror->output_port);
>     >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror);
>     >>> + ovsrec_port_delete(ovs_mirror->output_port);
>     >>> + ovsrec_mirror_delete(ovs_mirror);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    struct shash_node *mirror_node;
>     >>> +    const struct sbrec_port_binding *sb_pb;
>     >>> +    int attach_cnt = 0;
>     >>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>     >>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>     >>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>     >>> +            /* Find if the mirror has other sources */
>     >>> +            attach_cnt = 0;
>     >>> + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>     >>> + pm_ctx->port_binding_table) {
>     >>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules;
>     i++) {
>     >>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
>     >>> + ovs_mirror->name)) {
>     >>> +                        attach_cnt++;
>     >>> +                    }
>     >>> +                }
>     >>> +            }
>     >>> +            if (attach_cnt) {
>     >>> +                /* More than 1 source then just
>     >>> +                 * update the mirror table
>     >>> +                 */
>     >>> +                bool done = false;
>     >>> +                for (size_t i = 0; ((i <
>     >>> ovs_mirror->n_select_dst_port)
>     >>> + && (done ==
>     >>> false)); i++) {
>     >>> +                    const struct ovsrec_port *port_rec =
>     >>> + ovs_mirror->select_dst_port[i];
>     >>> +                    for (size_t j = 0; j <
>     port_rec->n_interfaces;
>     >>> j++) {
>     >>> +                        const struct ovsrec_interface *iface_rec;
>     >>> +
>     >>> +                        iface_rec = port_rec->interfaces[j];
>     >>> +                        const char *iface_id =
>     >>> + smap_get(&iface_rec->external_ids,
>     >>> + "iface-id");
>     >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>     >>> + ovsrec_mirror_update_select_dst_port_delvalue(
>     >>> + ovs_mirror, port_rec);
>     >>> +                            done = true;
>     >>> +                            break;
>     >>> +                        }
>     >>> +                    }
>     >>> +                }
>     >>> +                done = false;
>     >>> +                for (size_t i = 0; ((i <
>     >>> ovs_mirror->n_select_src_port)
>     >>> + && (done ==
>     >>> false)); i++) {
>     >>> +                    const struct ovsrec_port *port_rec =
>     >>> + ovs_mirror->select_src_port[i];
>     >>> +                    for (size_t j = 0; j <
>     port_rec->n_interfaces;
>     >>> j++) {
>     >>> +                        const struct ovsrec_interface *iface_rec;
>     >>> +
>     >>> +                        iface_rec = port_rec->interfaces[j];
>     >>> +                        const char *iface_id =
>     >>> + smap_get(&iface_rec->external_ids,
>     >>> + "iface-id");
>     >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>     >>> + ovsrec_mirror_update_select_src_port_delvalue(
>     >>> + ovs_mirror, port_rec);
>     >>> +                            done = true;
>     >>> +                            break;
>     >>> +                        }
>     >>> +                    }
>     >>> +                }
>     >>> +            } else {
>     >>> +                /*
>     >>> +                 * If only 1 source delete the output port
>     >>> +                 * and then delete the mirror completely
>     >>> +                 */
>     >>> +                VLOG_INFO("Only 1 source for the mirror. Hence
>     >>> delete it");
>     >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror->output_port);
>     >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror);
>     >>> + ovsrec_port_delete(ovs_mirror->output_port);
>     >>> + ovsrec_mirror_delete(ovs_mirror);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    const char *used_node, *used_next;
>     >>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>     >>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>     >>> +    }
>     >>> +    sset_destroy(&pb_mirrors);
>     >>> +
>     >>> +    return true;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>     >>> +                            struct port_mirror_ctx *pm_ctx,
>     >>> +                            struct shash *pb_mirror_map)
>     >>> +{
>     >>> +    const struct ovsrec_mirror *mirror = NULL;
>     >>> +
>     >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>     >>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>     >>> +            const struct ovsrec_port *port_rec =
>     >>> mirror->select_dst_port[i];
>     >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>     >>> +                const struct ovsrec_interface *iface_rec;
>     >>> +                iface_rec = port_rec->interfaces[j];
>     >>> +                const char *logical_port =
>     >>> + smap_get(&iface_rec->external_ids, "iface-id");
>     >>> +                if (!strcmp(logical_port, pb->logical_port)) {
>     >>> + shash_add_once(pb_mirror_map, mirror->name,
>     >>> mirror);
>     >>> +                }
>     >>> +            }
>     >>> +        }
>     >>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>     >>> +            const struct ovsrec_port *port_rec =
>     >>> mirror->select_src_port[i];
>     >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>     >>> +                const struct ovsrec_interface *iface_rec;
>     >>> +                iface_rec = port_rec->interfaces[j];
>     >>> +                const char *logical_port =
>     >>> + smap_get(&iface_rec->external_ids, "iface-id");
>     >>> +                if (!strcmp(logical_port, pb->logical_port)) {
>     >>> + shash_add_once(pb_mirror_map, mirror->name,
>     >>> mirror);
>     >>> +                }
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +void
>     >>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>     >>> +{
>     >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>     >>> +
>     >>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>     >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>     >>> +    ovsdb_idl_add_column(ovs_idl,
>     &ovsrec_mirror_col_output_port);
>     >>> +    ovsdb_idl_add_column(ovs_idl,
>     &ovsrec_mirror_col_select_dst_port);
>     >>> +    ovsdb_idl_add_column(ovs_idl,
>     &ovsrec_mirror_col_select_src_port);
>     >>> +}
>     >>> +
>     >>> +
>     >>> +void
>     >>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
>     >>> +{
>     >>> +    shash_init(ovs_mirrors);
>     >>> +}
>     >>> +
>     >>> +void
>     >>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>     >>> +{
>     >>> +    const struct sbrec_port_binding *pb;
>     >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>     >>> + pm_ctx->port_binding_table) {
>     >>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +bool
>     >>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding
>     *pb,
>     >>> bool removed,
>     >>> +                     struct port_mirror_ctx *pm_ctx)
>     >>> +{
>     >>> +    bool ret = true;
>     >>> +    struct local_binding *lbinding = local_binding_find(
>     >>> + pm_ctx->local_bindings,
>     >>> pb->logical_port);
>     >>> +
>     >>> +    if (strcmp(pb->type, "") && (!lbinding)) {
>     >>> +        return ret;
>     >>> +    }
>     >>> +
>     >>> +    struct shash port_ovs_mirrors =
>     >>> SHASH_INITIALIZER(&port_ovs_mirrors);
>     >>> +
>     >>> +    /* Need to find if mirror needs update */
>     >>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>     >>> +    if (!removed) {
>     >>> +        if ((pb->n_mirror_rules == 0)
>     >>> +              && (shash_is_empty(&port_ovs_mirrors))) {
>     >>> +            /* No mirror update */
>     >>> +        } else if (pb->n_mirror_rules ==
>     >>> shash_count(&port_ovs_mirrors)) {
>     >>> +            /* Though number of mirror rules are same,
>     >>> +             * need to verify the contents
>     >>> +             */
>     >>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
>     >>> +                if (!shash_find(&port_ovs_mirrors,
>     >>> + pb->mirror_rules[i]->name)) {
>     >>> +                    /* Mis match in OVN SB DB and OVS DB
>     >>> +                     * Delete and Create mirror(s) with
>     proper sources
>     >>> +                     */
>     >>> +                    ret = mirror_delete(pb, pm_ctx,
>     >>> + &port_ovs_mirrors, false);
>     >>> +                    if (ret) {
>     >>> +                        ret = mirror_create(pb, pm_ctx);
>     >>> +                    }
>     >>> +                    break;
>     >>> +                }
>     >>> +            }
>     >>> +        } else {
>     >>> +            /* Update Mirror */
>     >>> +            if (pb->n_mirror_rules >
>     shash_count(&port_ovs_mirrors)) {
>     >>> +                /* create mirror,
>     >>> +                 * if mirror already exists only update selection
>     >>> +                 */
>     >>> +                ret = mirror_create(pb, pm_ctx);
>     >>> +            } else {
>     >>> +                /* delete mirror,
>     >>> +                 * if mirror has other sources only update
>     selection
>     >>> +                 */
>     >>> +                ret = mirror_delete(pb, pm_ctx,
>     &port_ovs_mirrors,
>     >>> false);
>     >>> +            }
>     >>> +        }
>     >>> +    } else {
>     >>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>     >>> +    }
>     >>> +
>     >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>     >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>     >>> + &port_ovs_mirrors) {
>     >>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>     >>> +    }
>     >>> +    shash_destroy(&port_ovs_mirrors);
>     >>> +
>     >>> +    return ret;
>     >>> +}
>     >>> +
>     >>> +bool
>     >>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>     >>> +{
>     >>> +    const struct sbrec_mirror *mirror = NULL;
>     >>> +    struct ovsrec_mirror *ovs_mirror = NULL;
>     >>> +
>     >>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
>     >>> pm_ctx->sb_mirror_table) {
>     >>> +    /* For each tracked mirror entry check if OVS entry is
>     there*/
>     >>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>     >>> mirror->name);
>     >>> +        if (ovs_mirror) {
>     >>> +            if (sbrec_mirror_is_deleted(mirror)) {
>     >>> +                /* Need to delete the mirror in OVS */
>     >>> +                VLOG_INFO("Delete mirror and remove port");
>     >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror->output_port);
>     >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>     >>> + ovs_mirror);
>     >>> + ovsrec_port_delete(ovs_mirror->output_port);
>     >>> + ovsrec_mirror_delete(ovs_mirror);
>     >>> +            } else {
>     >>> +                mirror_update(mirror, ovs_mirror);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    return true;
>     >>> +}
>     >>> +
>     >>> +void
>     >>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>     >>> +{
>     >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>     >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>     >>> + ovs_mirrors) {
>     >>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
>     >>> +    }
>     >>> +    shash_destroy(ovs_mirrors);
>     >>> +}
>     >>> diff --git a/controller/mirror.h b/controller/mirror.h
>     >>> new file mode 100644
>     >>> index 000000000..85b964f55
>     >>> --- /dev/null
>     >>> +++ b/controller/mirror.h
>     >>> @@ -0,0 +1,53 @@
>     >>> +/* Copyright (c) 2022 Red Hat, Inc.
>     >>> + *
>     >>> + * Licensed under the Apache License, Version 2.0 (the
>     "License");
>     >>> + * you may not use this file except in compliance with the
>     License.
>     >>> + * You may obtain a copy of the License at:
>     >>> + *
>     >>> + * http://www.apache.org/licenses/LICENSE-2.0
>     >>> + *
>     >>> + * Unless required by applicable law or agreed to in writing,
>     software
>     >>> + * distributed under the License is distributed on an "AS IS"
>     BASIS,
>     >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>     express or
>     >>> implied.
>     >>> + * See the License for the specific language governing
>     permissions and
>     >>> + * limitations under the License.
>     >>> + */
>     >>> +
>     >>> +#ifndef OVN_MIRROR_H
>     >>> +#define OVN_MIRROR_H 1
>     >>> +
>     >>> +struct ovsdb_idl_txn;
>     >>> +struct ovsrec_port_table;
>     >>> +struct ovsrec_bridge;
>     >>> +struct ovsrec_bridge_table;
>     >>> +struct ovsrec_open_vswitch_table;
>     >>> +struct sbrec_chassis;
>     >>> +struct ovsrec_interface_table;
>     >>> +struct ovsrec_mirror_table;
>     >>> +struct sbrec_mirror_table;
>     >>> +struct sbrec_port_binding_table;
>     >>> +
>     >>> +struct port_mirror_ctx {
>     >>> +    struct shash *ovs_mirrors;
>     >>> +    struct ovsdb_idl_txn *ovs_idl_txn;
>     >>> +    const struct ovsrec_port_table *port_table;
>     >>> +    const struct ovsrec_bridge *br_int;
>     >>> +    const struct sbrec_chassis *chassis_rec;
>     >>> +    const struct ovsrec_bridge_table *bridge_table;
>     >>> +    const struct ovsrec_open_vswitch_table *ovs_table;
>     >>> +    const struct ovsrec_interface_table *iface_table;
>     >>> +    const struct ovsrec_mirror_table *mirror_table;
>     >>> +    const struct sbrec_mirror_table *sb_mirror_table;
>     >>> +    const struct sbrec_port_binding_table *port_binding_table;
>     >>> +    struct shash *local_bindings;
>     >>> +};
>     >>> +
>     >>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
>     >>> +void ovn_port_mirror_init(struct shash *);
>     >>> +void ovn_port_mirror_destroy(struct shash *);
>     >>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>     >>> +bool ovn_port_mirror_handle_lport(const struct
>     sbrec_port_binding *pb,
>     >>> +                                  bool removed,
>     >>> +                                  struct port_mirror_ctx
>     *pm_ctx);
>     >>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx
>     *pm_ctx);
>     >>> +#endif
>     >>> diff --git a/controller/ovn-controller.c
>     b/controller/ovn-controller.c
>     >>> index 8895c7a2b..15ab17c4a 100644
>     >>> --- a/controller/ovn-controller.c
>     >>> +++ b/controller/ovn-controller.c
>     >>> @@ -78,6 +78,7 @@
>     >>>   #include "lib/inc-proc-eng.h"
>     >>>   #include "lib/ovn-l7.h"
>     >>>   #include "hmapx.h"
>     >>> +#include "mirror.h"
>     >>>
>     >>>   VLOG_DEFINE_THIS_MODULE(main);
>     >>>
>     >>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl
>     *ovs_idl)
>     >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>     >>>       ovsdb_idl_track_add_column(ovs_idl,
>     &ovsrec_port_col_interfaces);
>     >>>       ovsdb_idl_track_add_column(ovs_idl,
>     >>> &ovsrec_port_col_external_ids);
>     >>> +    mirror_register_ovs_idl(ovs_idl);
>     >>>   }
>     >>>
>     >>>   #define SB_NODES \
>     >>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl
>     *ovs_idl)
>     >>>       SB_NODE(load_balancer, "load_balancer") \
>     >>>       SB_NODE(fdb, "fdb") \
>     >>>       SB_NODE(meter, "meter") \
>     >>> +    SB_NODE(mirror, "mirror") \
>     >>>       SB_NODE(static_mac_binding, "static_mac_binding")
>     >>>
>     >>>   enum sb_engine_node {
>     >>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>     >>>       OVS_NODE(bridge, "bridge") \
>     >>>       OVS_NODE(port, "port") \
>     >>>       OVS_NODE(interface, "interface") \
>     >>> -    OVS_NODE(qos, "qos")
>     >>> +    OVS_NODE(qos, "qos") \
>     >>> +    OVS_NODE(mirror, "mirror")
>     >>>
>     >>>   enum ovs_engine_node {
>     >>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>     >>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct
>     hmap *lbs)
>     >>>       free(lbs);
>     >>>   }
>     >>>
>     >>> +/* Mirror Engine */
>     >>> +struct ed_type_port_mirror {
>     >>> +    struct shash ovs_mirrors;
>     >>> +};
>     >>> +
>     >>> +static void *
>     >>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>     >>> +                    struct engine_arg *arg OVS_UNUSED)
>     >>> +{
>     >>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof
>     >>> *port_mirror);
>     >>> + ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>     >>> +    return port_mirror;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +en_port_mirror_cleanup(void *data)
>     >>> +{
>     >>> +    struct ed_type_port_mirror *port_mirror = data;
>     >>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +init_port_mirror_ctx(struct engine_node *node,
>     >>> +                 struct ed_type_runtime_data *rt_data,
>     >>> +                 struct ed_type_port_mirror *port_mirror_data,
>     >>> +                 struct port_mirror_ctx *pm_ctx)
>     >>> +{
>     >>> +    struct ovsrec_open_vswitch_table *ovs_table =
>     >>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>     >>> + engine_get_input("OVS_open_vswitch", node));
>     >>> +    struct ovsrec_bridge_table *bridge_table =
>     >>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>     >>> +            engine_get_input("OVS_bridge", node));
>     >>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>     >>> +    const struct ovsrec_bridge *br_int =
>     get_br_int(bridge_table,
>     >>> ovs_table);
>     >>> +
>     >>> +    ovs_assert(br_int && chassis_id);
>     >>> +    const struct sbrec_chassis *chassis = NULL;
>     >>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>     >>> +        engine_ovsdb_node_get_index(
>     >>> +                engine_get_input("SB_chassis", node),
>     >>> +                "name");
>     >>> +
>     >>> +    if (chassis_id) {
>     >>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name,
>     >>> chassis_id);
>     >>> +    }
>     >>> +    ovs_assert(chassis);
>     >>> +
>     >>> +    struct ovsrec_port_table *port_table =
>     >>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>     >>> +            engine_get_input("OVS_port", node));
>     >>> +
>     >>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
>     >>> + engine_get_input_data("ovs_interface_shadow", node);
>     >>> +
>     >>> +    struct ovsrec_mirror_table *mirror_table =
>     >>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>     >>> +            engine_get_input("OVS_mirror", node));
>     >>> +
>     >>> +    struct sbrec_port_binding_table *pb_table =
>     >>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>     >>> + engine_get_input("SB_port_binding", node));
>     >>> +
>     >>> +    struct sbrec_mirror_table *sb_mirror_table =
>     >>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>     >>> +            engine_get_input("SB_mirror", node));
>     >>> +
>     >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>     >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>     >>> + &port_mirror_data->ovs_mirrors) {
>     >>> + shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
>     >>> +    }
>     >>> +
>     >>> +    const struct ovsrec_mirror *ovsmirror = NULL;
>     >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>     >>> + shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
>     >>> ovsmirror);
>     >>> +    }
>     >>> +
>     >>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>     >>> +    pm_ctx->port_table = port_table;
>     >>> +    pm_ctx->iface_table = iface_shadow->iface_table;
>     >>> +    pm_ctx->mirror_table = mirror_table;
>     >>> +    pm_ctx->port_binding_table = pb_table;
>     >>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
>     >>> +    pm_ctx->br_int = br_int;
>     >>> +    pm_ctx->chassis_rec = chassis;
>     >>> +    pm_ctx->bridge_table = bridge_table;
>     >>> +    pm_ctx->ovs_table = ovs_table;
>     >>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>     >>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +en_port_mirror_run(struct engine_node *node, void *data)
>     >>> +{
>     >>> +    struct port_mirror_ctx pm_ctx;
>     >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>     >>> +    struct ed_type_runtime_data *rt_data =
>     >>> +        engine_get_input_data("runtime_data", node);
>     >>> +
>     >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data,
>     &pm_ctx);
>     >>> +
>     >>> +    ovn_port_mirror_run(&pm_ctx);
>     >>> +    engine_set_node_state(node, EN_UPDATED);
>     >>> +}
>     >>> +
>     >>> +static bool
>     >>> +port_mirror_runtime_data_handler(struct engine_node *node,
>     void *data)
>     >>> +{
>     >>> +    struct ed_type_runtime_data *rt_data =
>     >>> +        engine_get_input_data("runtime_data", node);
>     >>> +
>     >>> +    /* There is no tracked data. Fall back to full recompute of
>     >>> port_mirror */
>     >>> +    if (!rt_data->tracked) {
>     >>> +        return false;
>     >>> +    }
>     >>> +
>     >>> +    struct hmap *tracked_dp_bindings =
>     &rt_data->tracked_dp_bindings;
>     >>> +    if (hmap_is_empty(tracked_dp_bindings)) {
>     >>> +        return true;
>     >>> +    }
>     >>> +
>     >>> +    struct port_mirror_ctx pm_ctx;
>     >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>     >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data,
>     &pm_ctx);
>     >>> +
>     >>> +    struct tracked_datapath *tdp;
>     >>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>     >>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>     >>> +            /* Fall back to full recompute when a local datapath
>     >>> +             * is added or deleted. */
>     >>> +            return false;
>     >>> +        }
>     >>> +
>     >>> +        struct shash_node *shash_node;
>     >>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>     >>> +            struct tracked_lport *lport = shash_node->data;
>     >>> +            bool removed =
>     >>> +                lport->tracked_type ==
>     TRACKED_RESOURCE_REMOVED ?
>     >>> true: false;
>     >>> +            if (!ovn_port_mirror_handle_lport(lport->pb,
>     removed,
>     >>> &pm_ctx)) {
>     >>> +                return false;
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    engine_set_node_state(node, EN_UPDATED);
>     >>> +    return true;
>     >>> +}
>     >>> +
>     >>> +static bool
>     >>> +port_mirror_port_binding_handler(struct engine_node *node,
>     void *data)
>     >>> +{
>     >>> +    struct port_mirror_ctx pm_ctx;
>     >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>     >>> +    struct ed_type_runtime_data *rt_data =
>     >>> +        engine_get_input_data("runtime_data", node);
>     >>> +
>     >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data,
>     &pm_ctx);
>     >>> +
>     >>> +    /* handle port binding updates (i.,e when the mirror column
>     >>> +     * of port_binding is updated)
>     >>> +     */
>     >>> +    const struct sbrec_port_binding *pb;
>     >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>     >>> + pm_ctx.port_binding_table) {
>     >>> +        bool removed = sbrec_port_binding_is_deleted(pb);
>     >>> +        if (!ovn_port_mirror_handle_lport(pb, removed,
>     &pm_ctx)) {
>     >>> +            return false;
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    engine_set_node_state(node, EN_UPDATED);
>     >>> +    return true;
>     >>> +
>     >>> +}
>     >>> +
>     >>> +static bool
>     >>> +port_mirror_sb_mirror_handler(struct engine_node *node, void
>     *data)
>     >>> +{
>     >>> +    struct port_mirror_ctx pm_ctx;
>     >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>     >>> +    struct ed_type_runtime_data *rt_data =
>     >>> +        engine_get_input_data("runtime_data", node);
>     >>> +
>     >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data,
>     &pm_ctx);
>     >>> +
>     >>> +    /* handle sb mirror updates
>     >>> +     */
>     >>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>     >>> +        return false;
>     >>> +    }
>     >>> +
>     >>> +    engine_set_node_state(node, EN_UPDATED);
>     >>> +    return true;
>     >>> +
>     >>> +}
>     >>> +
>     >>>   /* Engine node which is used to handle the Non VIF data like
>     >>>    *   - OVS patch ports
>     >>>    *   - Tunnel ports and the related chassis information.
>     >>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>     >>> ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>     >>>       ENGINE_NODE(northd_options, "northd_options");
>     >>>       ENGINE_NODE(dhcp_options, "dhcp_options");
>     >>> +    ENGINE_NODE(port_mirror, "port_mirror");
>     >>>
>     >>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>     >>>       SB_NODES
>     >>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>     >>>       engine_add_input(&en_flow_output, &en_pflow_output,
>     >>> flow_output_pflow_output_handler);
>     >>>
>     >>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch,
>     NULL);
>     >>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>     >>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>     >>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>     >>> +    engine_add_input(&en_port_mirror, &en_ovs_port,
>     >>> engine_noop_handler);
>     >>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>     >>> +                     engine_noop_handler);
>     >>> +    engine_add_input(&en_flow_output, &en_port_mirror,
>     >>> +                     engine_noop_handler);
>     >>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
>     >>> + port_mirror_runtime_data_handler);
>     >>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>     >>> + port_mirror_sb_mirror_handler);
>     >>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>     >>> + port_mirror_port_binding_handler);
>     >>> +
>     >>>       struct engine_arg engine_arg = {
>     >>>           .sb_idl = ovnsb_idl_loop.idl,
>     >>>           .ovs_idl = ovs_idl_loop.idl,
>     >>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>     >>>
>     >>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>     >>> time_msec());
>     >>> -                    if (ovnsb_idl_txn) {
>     >>> -                        if (ofctrl_has_backlog()) {
>     >>> -                            /* When there are in-flight messages
>     >>> pending to
>     >>> -                             * ovs-vswitchd, we should hold on
>     >>> recomputing so
>     >>> -                             * that the previous flow
>     installations
>     >>> won't be
>     >>> -                             * delayed. However, we still
>     want to
>     >>> try if
>     >>> -                             * recompute is not needed and we
>     can
>     >>> quickly
>     >>> -                             * incrementally process the new
>     >>> changes, to avoid
>     >>> -                             * unnecessarily forced recomputes
>     >>> later on.  This
>     >>> -                             * is because the OVSDB change
>     tracker
>     >>> cannot
>     >>> -                             * preserve tracked changes across
>     >>> iterations.  If
>     >>> -                             * change tracking is improved,
>     we can
>     >>> simply skip
>     >>> -                             * this round of engine_run and
>     >>> continue processing
>     >>> -                             * acculated changes incrementally
>     >>> later when
>     >>> -                             * ofctrl_has_backlog() returns
>     false. */
>     >>> -                            engine_run(false);
>     >>> -                        } else {
>     >>> -                            engine_run(true);
>     >>> -                        }
>     >>> -                    } else {
>     >>> -                        /* Even if there's no SB DB transaction
>     >>> available,
>     >>> +
>     >>> +                    bool allow_engine_recompute = true;
>     >>> +
>     >>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>     >>> + ofctrl_has_backlog()) {
>     >>> +                        /* When there are in-flight messages
>     >>> pending to
>     >>> +                         * ovs-vswitchd, we should hold on
>     >>> recomputing so
>     >>> +                         * that the previous flow installations
>     >>> won't be
>     >>> +                         * delayed.  However, we still want
>     to try if
>     >>> +                         * recompute is not needed and we can
>     quickly
>     >>> +                         * incrementally process the new
>     changes,
>     >>> to avoid
>     >>> +                         * unnecessarily forced recomputes later
>     >>> on.  This
>     >>> +                         * is because the OVSDB change
>     tracker cannot
>     >>> +                         * preserve tracked changes across
>     >>> iterations.  If
>     >>> +                         * change tracking is improved, we can
>     >>> simply skip
>     >>> +                         * this round of engine_run and continue
>     >>> processing
>     >>> +                         * acculated changes incrementally
>     later when
>     >>> +                         * ofctrl_has_backlog() returns false. */
>     >>> +
>     >>> +                        /* Even if there's no SB/OVS DB
>     transaction
>     >>> available,
>     >>>                            * try to run the engine so that we can
>     >>> handle any
>     >>>                            * incremental changes that don't
>     require
>     >>> a recompute.
>     >>>                            * If a recompute is required, the
>     engine
>     >>> will abort,
>     >>>                            * triggerring a full run in the next
>     >>> iteration.
>     >>>                            */
>     >>> -                        engine_run(false);
>     >>> +                        allow_engine_recompute = false;
>     >>>                       }
>     >>> +
>     >>> + engine_run(allow_engine_recompute);
>     >>> +
>     >>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>     >>> time_msec());
>     >>>                       if (engine_has_updated()) {
>     >>> diff --git a/northd/en-northd.c b/northd/en-northd.c
>     >>> index 7fe83db64..608220b1f 100644
>     >>> --- a/northd/en-northd.c
>     >>> +++ b/northd/en-northd.c
>     >>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node,
>     void
>     >>> *data)
>     >>> EN_OVSDB_GET(engine_get_input("NB_acl", node));
>     >>>       input_data.nbrec_static_mac_binding_table =
>     >>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>     >>> +    input_data.nbrec_mirror_table =
>     >>> + EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>     >>>
>     >>>       input_data.sbrec_sb_global_table =
>     >>> EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>     >>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node,
>     >>> void *data)
>     >>> EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>     >>>       input_data.sbrec_static_mac_binding_table =
>     >>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>     >>> +    input_data.sbrec_mirror_table =
>     >>> + EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>     >>>
>     >>>       northd_run(&input_data, data,
>     >>>                  eng_ctx->ovnnb_idl_txn,
>     >>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>     >>> index 54e0ad3b0..ac27a730e 100644
>     >>> --- a/northd/inc-proc-northd.c
>     >>> +++ b/northd/inc-proc-northd.c
>     >>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>     >>>       NB_NODE(acl, "acl") \
>     >>>       NB_NODE(logical_router, "logical_router") \
>     >>>       NB_NODE(qos, "qos") \
>     >>> +    NB_NODE(mirror, "mirror") \
>     >>>       NB_NODE(meter, "meter") \
>     >>>       NB_NODE(meter_band, "meter_band") \
>     >>>       NB_NODE(logical_router_port, "logical_router_port") \
>     >>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>     >>>       SB_NODE(logical_flow, "logical_flow") \
>     >>>       SB_NODE(logical_dp_group, "logical_DP_group") \
>     >>>       SB_NODE(multicast_group, "multicast_group") \
>     >>> +    SB_NODE(mirror, "mirror") \
>     >>>       SB_NODE(meter, "meter") \
>     >>>       SB_NODE(meter_band, "meter_band") \
>     >>>       SB_NODE(datapath_binding, "datapath_binding") \
>     >>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct
>     ovsdb_idl_loop
>     >>> *nb,
>     >>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
>     >>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>     >>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
>     >>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>     >>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
>     >>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>     >>>       engine_add_input(&en_northd, &en_nb_logical_router_port,
>     NULL);
>     >>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct
>     ovsdb_idl_loop
>     >>> *nb,
>     >>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
>     >>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
>     >>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>     >>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>     >>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
>     >>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>     >>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>     >>> diff --git a/northd/northd.c b/northd/northd.c
>     >>> index b7388afc5..52abdda28 100644
>     >>> --- a/northd/northd.c
>     >>> +++ b/northd/northd.c
>     >>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>     >>>       free(requested_chassis_sb);
>     >>>   }
>     >>>
>     >>> +static void
>     >>> +do_sb_mirror_addition(struct northd_input *input_data,
>     >>> +                      const struct ovn_port *op)
>     >>> +{
>     >>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>     >>> +        const struct sbrec_mirror *sb_mirror;
>     >>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>     >>> + input_data->sbrec_mirror_table) {
>     >>> +            if (!strcmp(sb_mirror->name,
>     >>> + op->nbsp->mirror_rules[i]->name)) {
>     >>> +                /* Add the value to SB */
>     >>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>     >>> + sb_mirror);
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +sbrec_port_binding_update_mirror_rules(struct northd_input
>     >>> *input_data,
>     >>> +                                       const struct ovn_port *op)
>     >>> +{
>     >>> +    size_t i = 0;
>     >>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>     >>> +        /* Needs deletion in SB */
>     >>> +        struct shash nb_mirror_rules =
>     >>> SHASH_INITIALIZER(&nb_mirror_rules);
>     >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>     >>> +            shash_add(&nb_mirror_rules,
>     >>> + op->nbsp->mirror_rules[i]->name,
>     >>> + op->nbsp->mirror_rules[i]);
>     >>> +        }
>     >>> +
>     >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>     >>> +            if (!shash_find(&nb_mirror_rules,
>     >>> + op->sb->mirror_rules[i]->name)) {
>     >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>     >>> + op->sb->mirror_rules[i]);
>     >>> +            }
>     >>> +        }
>     >>> +
>     >>> +        struct shash_node *node, *next;
>     >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>     >>> +            shash_delete(&nb_mirror_rules, node);
>     >>> +        }
>     >>> +        shash_destroy(&nb_mirror_rules);
>     >>> +
>     >>> +    } else if (op->sb->n_mirror_rules <
>     op->nbsp->n_mirror_rules) {
>     >>> +        /* Needs addition in SB */
>     >>> +        do_sb_mirror_addition(input_data, op);
>     >>> +    } else if (op->sb->n_mirror_rules ==
>     op->nbsp->n_mirror_rules) {
>     >>> +        /*
>     >>> +         * Check if its the same mirrors on both SB and NB DBs
>     >>> +         * If not update accordingly.
>     >>> +         */
>     >>> +        bool needs_sb_addition = false;
>     >>> +        struct shash nb_mirror_rules =
>     >>> SHASH_INITIALIZER(&nb_mirror_rules);
>     >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>     >>> +            shash_add(&nb_mirror_rules,
>     >>> + op->nbsp->mirror_rules[i]->name,
>     >>> + op->nbsp->mirror_rules[i]);
>     >>> +        }
>     >>> +
>     >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>     >>> +            if (!shash_find(&nb_mirror_rules,
>     >>> + op->sb->mirror_rules[i]->name)) {
>     >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>     >>> + op->sb->mirror_rules[i]);
>     >>> +                    needs_sb_addition = true;
>     >>> +            }
>     >>> +        }
>     >>> +
>     >>> +        struct shash_node *node, *next;
>     >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>     >>> +            shash_delete(&nb_mirror_rules, node);
>     >>> +        }
>     >>> +        shash_destroy(&nb_mirror_rules);
>     >>> +
>     >>> +        if (needs_sb_addition) {
>     >>> +            do_sb_mirror_addition(input_data, op);
>     >>> +        }
>     >>> +    }
>     >>> +}
>     >>> +
>     >>>   static void
>     >>>   ovn_port_update_sbrec(struct northd_input *input_data,
>     >>>                         struct ovsdb_idl_txn *ovnsb_txn,
>     >>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input
>     >>> *input_data,
>     >>>           }
>     >>> sbrec_port_binding_set_external_ids(op->sb, &ids);
>     >>>           smap_destroy(&ids);
>     >>> +
>     >>> +        if (!op->nbsp->n_mirror_rules) {
>     >>> +            /* Nothing is set. Clear mirror_rules from pb. */
>     >>> + sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>     >>> +        } else {
>     >>> +            /* Check if SB DB update needed */
>     >>> + sbrec_port_binding_update_mirror_rules(input_data, op);
>     >>> +        }
>     >>> +
>     >>>       }
>     >>>       if (op->tunnel_key != op->sb->tunnel_key) {
>     >>> sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>     >>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input
>     *input_data,
>     >>>       shash_destroy(&sb_meters);
>     >>>   }
>     >>>
>     >>> +static bool
>     >>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>     >>> +                  const struct sbrec_mirror *sb_mirror)
>     >>> +{
>     >>> +
>     >>> +    if (nb_mirror->index != sb_mirror->index) {
>     >>> +        return true;
>     >>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>     >>> +        return true;
>     >>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>     >>> +        return true;
>     >>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>     >>> +        return true;
>     >>> +    }
>     >>> +
>     >>> +    return false;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>     >>> +                             const char *mirror_name,
>     >>> +                             const struct nbrec_mirror
>     *nb_mirror,
>     >>> +                             struct shash *sb_mirrors,
>     >>> +                             struct sset *used_sb_mirrors)
>     >>> +{
>     >>> +    const struct sbrec_mirror *sb_mirror;
>     >>> +    bool new_sb_mirror = false;
>     >>> +
>     >>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>     >>> +    if (!sb_mirror) {
>     >>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>     >>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>     >>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>     >>> +        new_sb_mirror = true;
>     >>> +    }
>     >>> +    sset_add(used_sb_mirrors, mirror_name);
>     >>> +
>     >>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror,
>     >>> sb_mirror)) {
>     >>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>     >>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>     >>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>     >>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +sync_mirrors(struct northd_input *input_data,
>     >>> +            struct ovsdb_idl_txn *ovnsb_txn)
>     >>> +{
>     >>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>     >>> +    struct sset used_sb_mirrors =
>     SSET_INITIALIZER(&used_sb_mirrors);
>     >>> +
>     >>> +    const struct sbrec_mirror *sb_mirror;
>     >>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>     >>> input_data->sbrec_mirror_table) {
>     >>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>     >>> +    }
>     >>> +
>     >>> +    const struct nbrec_mirror *nb_mirror;
>     >>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
>     >>> input_data->nbrec_mirror_table) {
>     >>> + sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
>     >>> nb_mirror,
>     >>> + &sb_mirrors, &used_sb_mirrors);
>     >>> +    }
>     >>> +
>     >>> +    const char *used_mirror;
>     >>> +    const char *used_mirror_next;
>     >>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
>     >>> &used_sb_mirrors) {
>     >>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
>     >>> +        sset_delete(&used_sb_mirrors,
>     >>> SSET_NODE_FROM_NAME(used_mirror));
>     >>> +    }
>     >>> +    sset_destroy(&used_sb_mirrors);
>     >>> +
>     >>> +    struct shash_node *node, *next;
>     >>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>     >>> +        sbrec_mirror_delete(node->data);
>     >>> +        shash_delete(&sb_mirrors, node);
>     >>> +    }
>     >>> +    shash_destroy(&sb_mirrors);
>     >>> +}
>     >>> +
>     >>>   /*
>     >>>    * struct 'dns_info' is used to sync the DNS records between
>     OVN
>     >>> Northbound db
>     >>>    * and Southbound db.
>     >>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input
>     *input_data,
>     >>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>     >>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>     >>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>     >>> +    sync_mirrors(input_data, ovnsb_txn);
>     >>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>     >>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
>     >>> stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>     >>> diff --git a/northd/northd.h b/northd/northd.h
>     >>> index da90e2815..17a62ea10 100644
>     >>> --- a/northd/northd.h
>     >>> +++ b/northd/northd.h
>     >>> @@ -36,6 +36,7 @@ struct northd_input {
>     >>>       const struct nbrec_acl_table *nbrec_acl_table;
>     >>>       const struct nbrec_static_mac_binding_table
>     >>>           *nbrec_static_mac_binding_table;
>     >>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>     >>>
>     >>>       /* Southbound table references */
>     >>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
>     >>> @@ -55,6 +56,7 @@ struct northd_input {
>     >>>       const struct sbrec_chassis_private_table
>     >>> *sbrec_chassis_private_table;
>     >>>       const struct sbrec_static_mac_binding_table
>     >>>           *sbrec_static_mac_binding_table;
>     >>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>     >>>
>     >>>       /* Indexes */
>     >>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
>     >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>     >>> index 174364c8b..01de55222 100644
>     >>> --- a/ovn-nb.ovsschema
>     >>> +++ b/ovn-nb.ovsschema
>     >>> @@ -1,7 +1,7 @@
>     >>>   {
>     >>>       "name": "OVN_Northbound",
>     >>> -    "version": "6.3.0",
>     >>> -    "cksum": "4042813038 31869",
>     >>> +    "version": "6.4.0",
>     >>> +    "cksum": "589874483 33352",
>     >>>       "tables": {
>     >>>           "NB_Global": {
>     >>>               "columns": {
>     >>> @@ -132,6 +132,11 @@
>     >>> "refType": "weak"},
>     >>>                                    "min": 0,
>     >>>                                    "max": 1}},
>     >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>     >>> + "refTable": "Mirror",
>     >>> + "refType": "weak"},
>     >>> +                                  "min": 0,
>     >>> +                                  "max": "unlimited"}},
>     >>>                   "ha_chassis_group": {
>     >>>                       "type": {"key": {"type": "uuid",
>     >>> "refTable": "HA_Chassis_Group",
>     >>> @@ -301,6 +306,28 @@
>     >>>                       "type": {"key": "string", "value": "string",
>     >>>                                "min": 0, "max": "unlimited"}}},
>     >>>               "isRoot": false},
>     >>> +        "Mirror": {
>     >>> +            "columns": {
>     >>> +                "name": {"type": "string"},
>     >>> +                "filter": {"type": {"key": {"type": "string",
>     >>> + "enum": ["set",
>     >>> ["from-lport",
>     >>> + "to-lport",
>     >>> + "both"]]}}},
>     >>> +                "sink":{"type": "string"},
>     >>> +                "type": {"type": {"key": {"type": "string",
>     >>> + "enum": ["set", ["gre",
>     >>> + "erspan"]]}}},
>     >>> +                "index": {"type": "integer"},
>     >>> +                "src": {"type": {"key": {"type": "uuid",
>     >>> + "refTable":
>     >>> "Logical_Switch_Port",
>     >>> + "refType": "weak"},
>     >>> +                                   "min": 0,
>     >>> +                                   "max": "unlimited"}},
>     >>> +                "external_ids": {
>     >>> +                    "type": {"key": "string", "value": "string",
>     >>> +                             "min": 0, "max": "unlimited"}}},
>     >>> +            "indexes": [["name"]],
>     >>> +            "isRoot": true},
>     >>>           "Meter": {
>     >>>               "columns": {
>     >>>                   "name": {"type": "string"},
>     >>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>     >>> index f41e9d7c0..d8730c8fc 100644
>     >>> --- a/ovn-nb.xml
>     >>> +++ b/ovn-nb.xml
>     >>> @@ -1554,6 +1554,11 @@
>     >>>         </column>
>     >>>       </group>
>     >>>
>     >>> +    <column name="mirror_rules">
>     >>> +        Mirror rules that apply to logical switch port which
>     is the
>     >>> source.
>     >>> +        Please see the <ref table="Mirror"/> table.
>     >>> +    </column>
>     >>> +
>     >>>       <column name="ha_chassis_group">
>     >>>         References a row in the OVN Northbound database's
>     >>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>     >>> @@ -2491,6 +2496,64 @@
>     >>>       </column>
>     >>>     </table>
>     >>>
>     >>> +  <table name="Mirror" title="Mirror Entry">
>     >>> +    <p>
>     >>> +      Each row in this table represents one Mirror that can
>     be used
>     >>> for
>     >>> +      port mirroring. These Mirrors are referenced by the
>     >>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/>
>     >>> column in
>     >>> +      the <ref table="Logical_Switch_Port"/> table.
>     >>> +    </p>
>     >>> +
>     >>> +    <column name="name">
>     >>> +      <p>
>     >>> +        Represents the name of the mirror.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="filter">
>     >>> +      <p>
>     >>> +        The value of this field represents selection criteria of
>     >>> the mirror.
>     >>> +        Supported values for filter to-lport / from-lport / both
>     >>> +        to-lport - to mirror packets coming into logical port
>     >>> +        from-lport - to mirror packets going out of logical port
>     >>> +        both - to mirror packets coming into and going out of
>     >>> logical port.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="sink">
>     >>> +      <p>
>     >>> +        The value of this field represents the
>     destination/sink of
>     >>> the mirror.
>     >>> +        The value it takes is an IP address of the sink port.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="type">
>     >>> +      <p>
>     >>> +        The value of this field represents the type of the
>     tunnel
>     >>> used for
>     >>> +        sending the mirrored packets. Supported Tunnel types gre
>     >>> and erspan
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="index">
>     >>> +      <p>
>     >>> +        The value of this field represents the tunnel ID.
>     Depending
>     >>> on the
>     >>> +        tunnel type configured, GRE key value if type GRE and
>     >>> erspan_idx value
>     >>> +        if ERSPAN
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="src">
>     >>> +      <p>
>     >>> +        The value of this field represents a list of source
>     ports
>     >>> for the
>     >>> +        mirror. Please see the <ref
>     table="Logical_Switch_Port"/>
>     >>> table.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="external_ids">
>     >>> +      See <em>External IDs</em> at the beginning of this
>     document.
>     >>> +    </column>
>     >>> +  </table>
>     >>> +
>     >>>     <table name="Meter" title="Meter entry">
>     >>>       <p>
>     >>>         Each row in this table represents a meter that can be
>     used
>     >>> for QoS or
>     >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>     >>> index 576ebbdeb..b83134416 100644
>     >>> --- a/ovn-sb.ovsschema
>     >>> +++ b/ovn-sb.ovsschema
>     >>> @@ -1,7 +1,7 @@
>     >>>   {
>     >>>       "name": "OVN_Southbound",
>     >>> -    "version": "20.25.0",
>     >>> -    "cksum": "53184112 28845",
>     >>> +    "version": "20.26.0",
>     >>> +    "cksum": "2344151793 30004",
>     >>>       "tables": {
>     >>>           "SB_Global": {
>     >>>               "columns": {
>     >>> @@ -142,6 +142,23 @@
>     >>>               "indexes": [["datapath", "tunnel_key"],
>     >>>                           ["datapath", "name"]],
>     >>>               "isRoot": true},
>     >>> +        "Mirror": {
>     >>> +            "columns": {
>     >>> +                "name": {"type": "string"},
>     >>> +                "filter": {"type": {"key": {"type": "string",
>     >>> + "enum": ["set",
>     >>> + ["from-lport",
>     >>> + "to-lport","both"]]}}},
>     >>> +                "sink":{"type": "string"},
>     >>> +                "type": {"type": {"key": {"type": "string",
>     >>> + "enum": ["set",
>     >>> + ["gre",
>     >>> "erspan"]]}}},
>     >>> +                "index": {"type": "integer"},
>     >>> +                "external_ids": {
>     >>> +                    "type": {"key": "string", "value": "string",
>     >>> +                             "min": 0, "max": "unlimited"}}},
>     >>> +            "indexes": [["name"]],
>     >>> +            "isRoot": true},
>     >>>           "Meter": {
>     >>>               "columns": {
>     >>>                   "name": {"type": "string"},
>     >>> @@ -230,6 +247,11 @@
>     >>> "refTable": "Encap",
>     >>> "refType": "weak"},
>     >>>                                       "min": 0, "max":
>     "unlimited"}},
>     >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>     >>> + "refTable": "Mirror",
>     >>> + "refType": "weak"},
>     >>> +                                  "min": 0,
>     >>> +                                  "max": "unlimited"}},
>     >>>                   "mac": {"type": {"key": "string",
>     >>>                                    "min": 0,
>     >>>                                    "max": "unlimited"}},
>     >>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>     >>> index 315d60853..05c0db6b4 100644
>     >>> --- a/ovn-sb.xml
>     >>> +++ b/ovn-sb.xml
>     >>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>     >>>       </column>
>     >>>     </table>
>     >>>
>     >>> +  <table name="Mirror" title="Mirror Entry">
>     >>> +    <p>
>     >>> +      Each row in this table represents one Mirror that can
>     be used
>     >>> for
>     >>> +      port mirroring. These Mirrors are referenced by the
>     >>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
>     >>> +      the <ref table="Port_Binding"/> table.
>     >>> +    </p>
>     >>> +
>     >>> +    <column name="name">
>     >>> +      <p>
>     >>> +        Represents the name of the mirror.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="filter">
>     >>> +      <p>
>     >>> +        The value of this field represents selection criteria of
>     >>> the mirror.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="sink">
>     >>> +      <p>
>     >>> +        The value of this field represents the
>     destination/sink of
>     >>> the mirror.
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="type">
>     >>> +      <p>
>     >>> +        The value of this field represents the type of the
>     tunnel
>     >>> used for
>     >>> +        sending the mirrored packets
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="index">
>     >>> +      <p>
>     >>> +        The value of this field represents the key/idx
>     depending on
>     >>> the
>     >>> +        tunnel type configured
>     >>> +      </p>
>     >>> +    </column>
>     >>> +
>     >>> +    <column name="external_ids">
>     >>> +      See <em>External IDs</em> at the beginning of this
>     document.
>     >>> +    </column>
>     >>> +  </table>
>     >>> +
>     >>>     <table name="Meter" title="Meter entry">
>     >>>       <p>
>     >>>         Each row in this table represents a meter that can be
>     used
>     >>> for QoS or
>     >>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>     >>>         </column>
>     >>>       </group>
>     >>>
>     >>> +    <column name="mirror_rules">
>     >>> +        Mirror rules that apply to the port binding.
>     >>> +        Please see the <ref table="Mirror"/> table.
>     >>> +    </column>
>     >>> +
>     >>>       <group title="Patch Options">
>     >>>         <p>
>     >>>           These options apply to logical ports with <ref
>     >>> column="type"/> of
>     >>> diff --git a/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>     b/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>     >>> index 4d480e357..d79f9d929 100644
>     >>> --- a/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>     >>> +++ b/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>     >>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>     >>>
>     >>>   dnl
>     >>>
>     ---------------------------------------------------------------------
>     >>>
>     >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>     >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
>     10.10.10.1])
>     >>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>     >>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport
>     10.10.10.3])
>     >>> +AT_CHECK([ovn-nbctl ls-add sw0])
>     >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>     >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>     >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>     >>> +
>     >>> +dnl Add duplicate mirror name
>     >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
>     >>> 10.10.10.5], [1], [], [stderr])
>     >>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>     >>> +
>     >>> +dnl Attach invalid source port to mirror
>     >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3],
>     [1], [],
>     >>> [stderr])
>     >>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>     >>> +
>     >>> +dnl Attach source port to invalid mirror
>     >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4],
>     [1], [],
>     >>> [stderr])
>     >>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
>     >>> +
>     >>> +dnl Attach source port to mirror
>     >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>     >>> +
>     >>> +dnl Attach one more source port to mirror
>     >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>     >>> +
>     >>> +dnl Verify if multiple ports are attached to the same mirror
>     properly
>     >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>     >>> +mirror1:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  10.10.10.1
>     >>> +  Filter   :  from-lport
>     >>> +  Index/Key:  0
>     >>> +  Sources  :  None attached
>     >>> +mirror2:
>     >>> +  Type     :  erspan
>     >>> +  Sink     :  10.10.10.2
>     >>> +  Filter   :  both
>     >>> +  Index/Key:  1
>     >>> +  Sources  :  None attached
>     >>> +mirror3:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  10.10.10.3
>     >>> +  Filter   :  to-lport
>     >>> +  Index/Key:  2
>     >>> +  Sources  :  sw0-port1  sw0-port3
>     >>> +])
>     >>> +
>     >>> +dnl Detach one source port from mirror
>     >>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>     >>> +
>     >>> +dnl Verify if detach source port from mirror happens properly
>     >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>     >>> +mirror1:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  10.10.10.1
>     >>> +  Filter   :  from-lport
>     >>> +  Index/Key:  0
>     >>> +  Sources  :  None attached
>     >>> +mirror2:
>     >>> +  Type     :  erspan
>     >>> +  Sink     :  10.10.10.2
>     >>> +  Filter   :  both
>     >>> +  Index/Key:  1
>     >>> +  Sources  :  None attached
>     >>> +mirror3:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  10.10.10.3
>     >>> +  Filter   :  to-lport
>     >>> +  Index/Key:  2
>     >>> +  Sources  :  sw0-port1
>     >>> +])
>     >>> +
>     >>> +dnl Delete a single mirror which has source attached.
>     >>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
>     >>> +
>     >>> +dnl Check if the detach happened from source properly
>     >>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1
>     mirror_rules
>     >>> |  cut -b 3], [0], [dnl
>     >>> +
>     >>> +])
>     >>> +
>     >>> +dnl Check if the mirror deleted properly
>     >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>     >>> +mirror1:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  10.10.10.1
>     >>> +  Filter   :  from-lport
>     >>> +  Index/Key:  0
>     >>> +  Sources  :  None attached
>     >>> +mirror2:
>     >>> +  Type     :  erspan
>     >>> +  Sink     :  10.10.10.2
>     >>> +  Filter   :  both
>     >>> +  Index/Key:  1
>     >>> +  Sources  :  None attached
>     >>> +])
>     >>> +
>     >>> +dnl Delete another mirror
>     >>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
>     >>> +
>     >>> +dnl Update the Sink address
>     >>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>     >>> +
>     >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>     >>> +mirror1:
>     >>> +  Type     :  gre
>     >>> +  Sink     :  192.168.1.13
>     >>> +  Filter   :  from-lport
>     >>> +  Index/Key:  0
>     >>> +  Sources  :  None attached
>     >>> +])
>     >>> +
>     >>> +dnl Delete all mirrors
>     >>> +AT_CHECK([ovn-nbctl mirror-del])
>     >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>     >>> +])])
>     >>> +
>     >>> +dnl
>     >>>
>     ---------------------------------------------------------------------
>     >>> +
>     >>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>     >>>   AT_CHECK([ovn-nbctl lr-add lr0])
>     >>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2],
>     >>> [1], [],
>     >>> diff --git a/tests/ovn-northd.at <http://ovn-northd.at>
>     b/tests/ovn-northd.at <http://ovn-northd.at>
>     >>> index 4f399eccb..4e6c268e4 100644
>     >>> --- a/tests/ovn-northd.at <http://ovn-northd.at>
>     >>> +++ b/tests/ovn-northd.at <http://ovn-northd.at>
>     >>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1}
>     >>> meter_me__${acl2}
>     >>>   AT_CLEANUP
>     >>>   ])
>     >>>
>     >>> +OVN_FOR_EACH_NORTHD_NO_HV([
>     >>> +AT_SETUP([Check NB-SB mirrors sync])
>     >>> +AT_KEYWORDS([mirrors])
>     >>> +ovn_start
>     >>> +
>     >>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both
>     10.10.10.2
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>     >>> +"10.10.10.2"
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>     >>> +erspan
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>     >>> +0
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>     >>> +both
>     >>> +])
>     >>> +
>     >>> +check ovn-nbctl --wait=sb \
>     >>> +    -- set mirror . sink=192.168.1.13
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>     >>> +"192.168.1.13"
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>     >>> +erspan
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>     >>> +0
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>     >>> +both
>     >>> +])
>     >>> +
>     >>> +check ovn-nbctl --wait=sb \
>     >>> +    -- set mirror . type=gre
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>     >>> +gre
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>     >>> +"192.168.1.13"
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>     >>> +0
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>     >>> +both
>     >>> +])
>     >>> +
>     >>> +check ovn-nbctl --wait=sb \
>     >>> +    -- set mirror . index=12
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>     >>> +12
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>     >>> +gre
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>     >>> +"192.168.1.13"
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>     >>> +both
>     >>> +])
>     >>> +
>     >>> +check ovn-nbctl --wait=sb \
>     >>> +    -- set mirror . filter=to-lport
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>     >>> +to-lport
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>     >>> +12
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>     >>> +gre
>     >>> +])
>     >>> +
>     >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>     >>> +"192.168.1.13"
>     >>> +])
>     >>> +
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>>   OVN_FOR_EACH_NORTHD_NO_HV([
>     >>>   AT_SETUP([ACL skip hints for stateless config])
>     >>>   AT_KEYWORDS([acl])
>     >>> diff --git a/tests/ovn.at <http://ovn.at> b/tests/ovn.at
>     <http://ovn.at>
>     >>> index f8b8db4df..cd5527ea1 100644
>     >>> --- a/tests/ovn.at <http://ovn.at>
>     >>> +++ b/tests/ovn.at <http://ovn.at>
>     >>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>     >>>   AT_CLEANUP
>     >>>   ])
>     >>>
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror])
>     >>> +AT_KEYWORDS([Mirror])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>     >>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>     >>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
>     >>> +    ofport-request=1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>     >>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>     >>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
>     >>> +    ofport-request=1
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +ovn-nbctl dump-flows > sbflows
>     >>> +AT_CAPTURE_FILE([sbflows])
>     >>> +
>     >>> +for i in 1 2; do
>     >>> +    : > vif$i.expected
>     >>> +done
>     >>> +
>     >>> +net_add n2
>     >>> +
>     >>> +sim_add hv2
>     >>> +as hv2
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:02:02:00\"
>     >>> +ovn_attach n2 br-phys 192.168.1.12
>     >>> +
>     >>> +OVN_POPULATE_ARP
>     >>> +
>     >>> +as hv1
>     >>> +
>     >>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC
>     IPV4_DST
>     >>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM]
>     ENCAP_TYPE FILTER
>     >>> +#
>     >>> +# Causes a packet to be received on INPORT. The packet is an
>     ICMPv4
>     >>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
>     >>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and
>     EXP_ICMP_CHKSUM are
>     >>> +# provided, then it should be the ip and icmp checksums of
>     the packet
>     >>> +# responded; otherwise, no reply is expected.
>     >>> +# In the absence of an ip checksum calculation helpers, this
>     relies
>     >>> +# on the caller to provide the checksums for the ip and icmp
>     headers.
>     >>> +# XXX This should be more systematic.
>     >>> +#
>     >>> +# INPORT is an lport number, e.g. 11 for vif11.
>     >>> +# ETH_SRC and ETH_DST are each 12 hex digits.
>     >>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>     >>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>     >>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>     >>> +# ENCAP_TYPE - gre or erspan
>     >>> +# FILTER - Mirror Filter - to-lport / from-lport
>     >>> +test_ipv4_icmp_request() {
>     >>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4
>     ipv4_dst=$5
>     >>> ip_chksum=$6 icmp_chksum=$7
>     >>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9
>     >>> mirror_encap_type=${10} mirror_filter=${11}
>     >>> +    shift; shift; shift; shift; shift; shift; shift
>     >>> +    shift; shift; shift; shift;
>     >>> +
>     >>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>     >>> +    local ip_ttl=02
>     >>> +    local icmp_id=5fbf
>     >>> +    local icmp_seq=0001
>     >>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>     >>> +    local icmp_type_code_request=0800
>     >>> +    local
>     >>>
>     icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>     >>> +    local
>     >>>
>     packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>     >>> +
>     >>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
>     >>> +
>     >>> +    # Expect to receive the reply, if any. In same port where
>     >>> packet was sent.
>     >>> +    # Note: src and dst fields are expected to be reversed.
>     >>> +    local icmp_type_code_response=0000
>     >>> +    local reply_icmp_ttl=fe
>     >>> +    local
>     >>>
>     reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>     >>> +    local
>     >>>
>     reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>     >>> +    echo $reply >> vif$inport.expected
>     >>> +    local remote_mac=000000020200
>     >>> +    local local_mac=000000010200
>     >>> +    if test ${mirror_encap_type} = "gre" ; then
>     >>> +        local
>     >>> ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>     >>> +        if test ${mirror_filter} = "to-lport" ; then
>     >>> +            local
>     >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>     >>> +        elif test ${mirror_filter} = "from-lport" ; then
>     >>> +            local
>     >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>     >>> +        fi
>     >>> +    elif test ${mirror_encap_type} = "erspan" ; then
>     >>> +        local
>     ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>     >>> +        local erspan_seq0=100088be000000001000000000000000
>     >>> +        local erspan_seq1=100088be000000011000000000000000
>     >>> +        if test ${mirror_filter} = "to-lport" ; then
>     >>> +            local
>     >>>
>     mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
>
>     >>>
>     >>> +        elif test ${mirror_filter} = "from-lport" ; then
>     >>> +            local
>     >>>
>     mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>     >>> +        fi
>     >>> +    fi
>     >>> +    echo $mirror >> br-phys_n1.expected
>     >>> +
>     >>> +}
>     >>> +
>     >>> +# Set IPs
>     >>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>     >>> +l1_ip=$(ip_to_hex 192 168 1 2)
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +
>     >>> +# Send ping packet and check for mirrored packet of the reply
>     >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>     >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>     >>> +
>     >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>     >>> [br-phys_n1.expected])
>     >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>     >>> +
>     >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>     >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>     >>> +rm -f br-phys_n1.expected
>     >>> +rm -f vif1.expected
>     >>> +
>     >>> +check ovn-nbctl set mirror . type=erspan
>     >>> +
>     >>> +# Send ping packet and check for mirrored packet of the reply
>     >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>     >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>     >>> +
>     >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>     >>> [br-phys_n1.expected])
>     >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>     >>> +
>     >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>     >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>     >>> +rm -f br-phys_n1.expected
>     >>> +rm -f vif1.expected
>     >>> +
>     >>> +check ovn-nbctl set mirror . filter=from-lport
>     >>> +
>     >>> +# Send ping packet and check for mirrored packet of the request
>     >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>     >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>     >>> +
>     >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>     >>> [br-phys_n1.expected])
>     >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>     >>> +
>     >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>     >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>     >>> +rm -f br-phys_n1.expected
>     >>> +rm -f vif1.expected
>     >>> +
>     >>> +check ovn-nbctl set mirror . type=gre
>     >>> +
>     >>> +# Send ping packet and check for mirrored packet of the request
>     >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>     >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>     >>> +
>     >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>     >>> [br-phys_n1.expected])
>     >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>     >>> +
>     >>> +echo "---------OVN NB Mirror-----"
>     >>> +ovn-nbctl mirror-list
>     >>> +
>     >>> +echo "---------OVS Mirror----"
>     >>> +ovs-vsctl list Mirror
>     >>> +
>     >>> +echo "-----------------------"
>     >>> +
>     >>> +echo "Verifying Mirror deletion in OVS"
>     >>> +# Set vif1 iface-id such that OVN releases port binding
>     >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>     >>> +])
>     >>> +
>     >>> +# Set vif1 iface-id back to ls1-lp1
>     >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0
>     name) =
>     >>> "mirror0"])
>     >>> +
>     >>> +# Delete vif1 so that OVN releases port binding
>     >>> +check ovs-vsctl del-port br-int vif1
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc
>     -l)])
>     >>> +
>     >>> +OVN_CLEANUP([hv1], [hv2])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror test bulk swap attachments])
>     >>> +AT_KEYWORDS([Mirror test bulk swap attachments])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls1-lp2 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>     >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp2 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>     >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif3 -- \
>     >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif4 -- \
>     >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +# Equal detaches and attaches
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>     >>> +
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>     >>> +
>     >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>     >>> +
>     >>> +OVN_CLEANUP([hv1])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror test bulk attach multiple])
>     >>> +AT_KEYWORDS([Mirror test bulk attach multiple])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls1-lp2 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>     >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp2 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>     >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif3 -- \
>     >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif4 -- \
>     >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +check ovn-nbctl mirror-del
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +
>     >>> +# Attaches multiple mirrors
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>     >>> +
>     >>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>     >>> +
>     >>> +OVN_CLEANUP([hv1])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror test bulk more detach and less attach])
>     >>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls1-lp2 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>     >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp2 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>     >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif3 -- \
>     >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif4 -- \
>     >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +check ovn-nbctl mirror-del
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +# Detaches more than attaches
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
>     >>> +
>     >>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
>     >>> +
>     >>> +OVN_CLEANUP([hv1])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror test bulk attach more than detach])
>     >>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls1-lp2 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>     >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp2 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>     >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif3 -- \
>     >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif4 -- \
>     >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +# Attaches more than detaches
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +as hv1
>     >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>     >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>     >>> +
>     >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>     >>> +
>     >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>     >>> +
>     >>> +OVN_CLEANUP([hv1])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>> +
>     >>> +OVN_FOR_EACH_NORTHD([
>     >>> +AT_SETUP([Mirror test bulk detach multiple])
>     >>> +AT_KEYWORDS([Mirror test bulk detach multiple])
>     >>> +ovn_start
>     >>> +
>     >>> +# Logical network:
>     >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>     <http://191.168.1.0/24>) connected to it,
>     >>> +# and has switch ls2 (172.16.1.0/24 <http://172.16.1.0/24>)
>     connected to it.
>     >>> +
>     >>> +ovn-nbctl lr-add R1
>     >>> +
>     >>> +ovn-nbctl ls-add ls1
>     >>> +ovn-nbctl ls-add ls2
>     >>> +
>     >>> +# Connect ls1 to R1
>     >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>     <http://192.168.1.1/24>
>     >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>     >>> +    type=router options:router-port=ls1
>     >>> addresses=\"00:00:00:01:02:f1\"
>     >>> +
>     >>> +# Connect ls2 to R1
>     >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>     <http://172.16.1.1/24>
>     >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>     >>> +    type=router options:router-port=ls2
>     >>> addresses=\"00:00:00:01:02:f2\"
>     >>> +
>     >>> +# Create logical port ls1-lp1 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>     >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>     >>> +
>     >>> +# Create logical port ls1-lp2 in ls1
>     >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>     >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>     >>> +
>     >>> +# Create logical port ls2-lp1 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>     >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>     >>> +
>     >>> +# Create logical port ls2-lp2 in ls2
>     >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>     >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>     >>> +
>     >>> +ovn-nbctl lsp-add ls1 ln-public
>     >>> +ovn-nbctl lsp-set-type ln-public localnet
>     >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>     >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>     >>> +
>     >>> +# Create one hypervisor and create OVS ports corresponding to
>     >>> logical ports.
>     >>> +net_add n1
>     >>> +
>     >>> +sim_add hv1
>     >>> +as hv1
>     >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>     >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>     >>> +ovn_attach n1 br-phys 192.168.1.11
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif1 -- \
>     >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif2 -- \
>     >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif3 -- \
>     >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>     >>> +
>     >>> +ovs-vsctl -- add-port br-int vif4 -- \
>     >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>     >>> +
>     >>> +ovs-vsctl set open .
>     external-ids:ovn-bridge-mappings=public:br-phys
>     >>> +
>     >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>     >>> +wait_for_ports_up
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>     >>> +
>     >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>     >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>     >>> +
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +# Detaches all
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>     >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>     >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>     >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>     >>> +check ovn-nbctl --wait=hv sync
>     >>> +
>     >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc
>     -l)])
>     >>> +
>     >>> +OVN_CLEANUP([hv1])
>     >>> +AT_CLEANUP
>     >>> +])
>     >>>
>     >>>   OVN_FOR_EACH_NORTHD([
>     >>>   AT_SETUP([Port Groups])
>     >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>     >>> index 811468dc6..af2e61435 100644
>     >>> --- a/utilities/ovn-nbctl.c
>     >>> +++ b/utilities/ovn-nbctl.c
>     >>> @@ -271,6 +271,19 @@ QoS commands:\n\
>     >>>                               remove QoS rules from SWITCH\n\
>     >>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>     >>>   \n\
>     >>> +Mirror commands:\n\
>     >>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
>     >>> +                            add a mirror with given name\n\
>     >>> +                            specify TYPE 'gre' or 'erspan'\n\
>     >>> +                            specify the tunnel INDEX value\n\
>     >>> +                                (indicates key if GRE\n\
>     >>> +                                 erpsan_idx if ERSPAN)\n\
>     >>> +                            specify FILTER for mirroring
>     selection\n\
>     >>> +                                'to-lport' / 'from-lport' /
>     'both'\n\
>     >>> +                            specify Sink / Destination i.e.
>     Remote
>     >>> IP\n\
>     >>> +  mirror-del [NAME]         remove mirrors\n\
>     >>> +  mirror-list               print mirrors\n\
>     >>> +\n\
>     >>>   Meter commands:\n\
>     >>>     [--fair]\n\
>     >>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
>     >>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>     >>>                               set dhcpv6 options for PORT\n\
>     >>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for
>     PORT\n\
>     >>>     lsp-get-ls PORT           get the logical switch which the
>     port
>     >>> belongs to\n\
>     >>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
>     >>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from
>     MIRROR\n\
>     >>>   \n\
>     >>>   Forwarding group commands:\n\
>     >>>     [--liveness]\n\
>     >>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context
>     *ctx)
>     >>>       ovsdb_idl_add_column(ctx->idl,
>     >>> &nbrec_logical_switch_port_col_type);
>     >>>   }
>     >>>
>     >>> +static void
>     >>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>     >>> +{
>     >>> +    ovsdb_idl_add_column(ctx->idl,
>     >>> &nbrec_logical_switch_port_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl,
>     >>> + &nbrec_logical_switch_port_col_mirror_rules);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>     >>> +}
>     >>> +
>     >>> +static int
>     >>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
>     >>> +{
>     >>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>     >>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>     >>> +
>     >>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
>     >>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
>     >>> +
>     >>> +    return strcmp(mirror1->name,mirror2->name);
>     >>> +}
>     >>> +
>     >>> +static char * OVS_WARN_UNUSED_RESULT
>     >>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>     >>> +                    bool must_exist,
>     >>> +                    const struct nbrec_mirror **mirror_p)
>     >>> +{
>     >>> +    const struct nbrec_mirror *mirror = NULL;
>     >>> +    *mirror_p = NULL;
>     >>> +
>     >>> +    struct uuid mirror_uuid;
>     >>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>     >>> +    if (is_uuid) {
>     >>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl,
>     &mirror_uuid);
>     >>> +    }
>     >>> +
>     >>> +    if (!mirror) {
>     >>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>     >>> +            if (!strcmp(mirror->name, id)) {
>     >>> +                break;
>     >>> +            }
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    if (!mirror && must_exist) {
>     >>> +        return xasprintf("%s: mirror %s not found",
>     >>> +                         id, is_uuid ? "UUID" : "name");
>     >>> +    }
>     >>> +
>     >>> +    *mirror_p = mirror;
>     >>> +    return NULL;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>     >>> +{
>     >>> +    const char *port = ctx->argv[1];
>     >>> +    const char *mirror_name = ctx->argv[2];
>     >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>     >>> +    const struct nbrec_mirror *mirror;
>     >>> +
>     >>> +    char *error;
>     >>> +
>     >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +
>     >>> +    /*check if a mirror rule actually exists on that name or
>     not*/
>     >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true,
>     &mirror);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +    /* Check if same mirror rule already exists for the lsp */
>     >>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>     >>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>     >>> +            bool may_exist = shash_find(&ctx->options,
>     >>> "--may-exist") != NULL;
>     >>> +            if (!may_exist) {
>     >>> +                ctl_error(ctx, "Same mirror already existed
>     on the
>     >>> lsp %s.",
>     >>> +                          ctx->argv[1]);
>     >>> +                return;
>     >>> +            }
>     >>> +            return;
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp,
>     mirror);
>     >>> + nbrec_mirror_update_src_addvalue(mirror,lsp);
>     >>> +
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>     >>> +{
>     >>> +    const char *port = ctx->argv[1];
>     >>> +    const char *mirror_name = ctx->argv[2];
>     >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>     >>> +    const struct nbrec_mirror *mirror;
>     >>> +
>     >>> +    char *error;
>     >>> +
>     >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +
>     >>> +    /*check if a mirror rule actually exists on that name or
>     not*/
>     >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true,
>     &mirror);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp,
>     mirror);
>     >>> + nbrec_mirror_update_src_delvalue(mirror,lsp);
>     >>> +
>     >>> +}
>     >>> +
>     >>>   static void
>     >>>   nbctl_lsp_set_type(struct ctl_context *ctx)
>     >>>   {
>     >>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct
>     >>> ctl_context *ctx)
>     >>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
>     >>>   }
>     >>>
>     >>> +static char * OVS_WARN_UNUSED_RESULT
>     >>> +parse_filter(const char *arg, const char **selection_p)
>     >>> +{
>     >>> +    /* Validate selection.  Only require the first letter. */
>     >>> +    if (arg[0] == 't') {
>     >>> +        *selection_p = "to-lport";
>     >>> +    } else if (arg[0] == 'f') {
>     >>> +        *selection_p = "from-lport";
>     >>> +    } else if (arg[0] == 'b') {
>     >>> +        *selection_p = "both";
>     >>> +    } else {
>     >>> +        *selection_p = NULL;
>     >>> +        return xasprintf("%s: selection must be \"to-lport\" or "
>     >>> +                         "\"from-lport\" or \"both\" ", arg);
>     >>> +    }
>     >>> +    return NULL;
>     >>> +}
>     >>> +
>     >>> +static char * OVS_WARN_UNUSED_RESULT
>     >>> +parse_type(const char *arg, const char **type_p)
>     >>> +{
>     >>> +    /* Validate type.  Only require the first letter. */
>     >>> +    if (arg[0] == 'g') {
>     >>> +        *type_p = "gre";
>     >>> +    } else if (arg[0] == 'e') {
>     >>> +        *type_p = "erspan";
>     >>> +    } else {
>     >>> +        *type_p = NULL;
>     >>> +        return xasprintf("%s: type must be \"gre\" or "
>     >>> +                         "\"erspan\"", arg);
>     >>> +    }
>     >>> +    return NULL;
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
>     >>> +{
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_mirror_add(struct ctl_context *ctx)
>     >>> +{
>     >>> +    const char *filter = NULL;
>     >>> +    const char *sink_ip = NULL;
>     >>> +    const char *type = NULL;
>     >>> +    const char *name = NULL;
>     >>> +    char *new_sink_ip = NULL;
>     >>> +    int64_t index;
>     >>> +    char *error = NULL;
>     >>> +    const struct nbrec_mirror *mirror_check = NULL;
>     >>> +
>     >>> +    /* Mirror Name */
>     >>> +    name = ctx->argv[1];
>     >>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>     >>> +        if (!strcmp(mirror_check->name, name)) {
>     >>> +            ctl_error(ctx, "Mirror with %s name already exists.",
>     >>> +                      name);
>     >>> +            return;
>     >>> +        }
>     >>> +    }
>     >>> +
>     >>> +    /* Tunnel Type - GRE/ERSPAN */
>     >>> +    error = parse_type(ctx->argv[2], &type);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +    /* tunnel index / GRE key / ERSPAN idx */
>     >>> +    index = atoi(ctx->argv[3]);
>     >> Shouldn't we validate the input is an actual number?
>     >>
>     >>> +
>     >>> +    /* Filter for mirroring */
>     >>> +    error = parse_filter(ctx->argv[4], &filter);
>     >>> +    if (error) {
>     >>> +        ctx->error = error;
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +    /* Destination / Sink details */
>     >>> +    sink_ip = ctx->argv[5];
>     >>> +
>     >>> +    /* check if it is a valid ip */
>     >>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>     >>> +    if (!new_sink_ip) {
>     >>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>     >>> +    }
>     >>> +
>     >>> +    if (new_sink_ip) {
>     >>> +        free(new_sink_ip);
>     >>> +    } else {
>     >>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +    /* Create the mirror. */
>     >>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>     >>> +    nbrec_mirror_set_name(mirror, name);
>     >>> +    nbrec_mirror_set_index(mirror, index);
>     >>> +    nbrec_mirror_set_filter(mirror, filter);
>     >>> +    nbrec_mirror_set_type(mirror, type);
>     >>> +    nbrec_mirror_set_sink(mirror, sink_ip);
>     >>> +
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
>     >>> +{
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_mirror_del(struct ctl_context *ctx)
>     >>> +{
>     >>> +    const struct nbrec_mirror *mirror, *next;
>     >>> +
>     >>> +    /* If a name is not specified, delete all mirrors. */
>     >>> +    if (ctx->argc == 1) {
>     >>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>     >>> +            nbrec_mirror_delete(mirror);
>     >>> +        }
>     >>> +        return;
>     >>> +    }
>     >>> +
>     >>> +    /* Remove the matching mirror. */
>     >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>     >>> +        if (strcmp(ctx->argv[1], mirror->name)) {
>     >>> +            continue;
>     >>> +        }
>     >>> +        nbrec_mirror_delete(mirror);
>     >>> +        return;
>     >>> +    }
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
>     >>> +{
>     >>> +    ovsdb_idl_add_column(ctx->idl,
>     >>> &nbrec_logical_switch_port_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>     >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>     >>> +}
>     >>> +
>     >>> +static void
>     >>> +nbctl_mirror_list(struct ctl_context *ctx)
>     >>> +{
>     >>> +
>     >>> +    const struct nbrec_mirror **mirrors = NULL;
>     >>> +    const struct nbrec_mirror *mirror;
>     >>> +    size_t n_capacity = 0;
>     >>> +    size_t n_mirrors = 0;
>     >>> +
>     >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>     >>> +        if (n_mirrors == n_capacity) {
>     >>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof
>     >>> *mirrors);
>     >>> +        }
>     >>> +
>     >>> +        mirrors[n_mirrors] = mirror;
>     >>> +        n_mirrors++;
>     >>> +    }
>     >>> +
>     >>> +    if (n_mirrors) {
>     >>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>     >>> +    }
>     >>> +
>     >>> +    for (size_t i = 0; i < n_mirrors; i++) {
>     >>> +        mirror = mirrors[i];
>     >>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>     >>> +        /* print all the values */
>     >>> +        ds_put_format(&ctx->output, " Type     : %s\n",
>     >>> mirror->type);
>     >>> +        ds_put_format(&ctx->output, " Sink     : %s\n",
>     >>> mirror->sink);
>     >>> +        ds_put_format(&ctx->output, " Filter   : %s\n",
>     >>> mirror->filter);
>     >>> +        ds_put_format(&ctx->output, " Index/Key: %ld\n",
>     >>> +                                                (long int)
>     >>> mirror->index);
>     >> You don't ned to cast if you pass %d formatter instead of %ld. The
>     >> same applies to other places in the patch where you cast to
>     long int.
>     >> In general, casting is not needed and should be avoided.
>     >>
>     >>> + ds_put_cstr(&ctx->output,   " Sources  :");
>     >>> +        if (mirror->n_src > 0) {
>     >>> +            struct svec srcs;
>     >>> +            const char *src;
>     >>> +            size_t j;
>     >>> +            svec_init(&srcs);
>     >>> +            for (j = 0; j < mirror->n_src; j++) {
>     >>> +                svec_add(&srcs, mirror->src[j]->name);
>     >>> +            }
>     >>> +            svec_sort(&srcs);
>     >>> +            SVEC_FOR_EACH (j, src, &srcs) {
>     >>> + ds_put_format(&ctx->output, "  %s", src);
>     >>> +            }
>     >>> +            svec_destroy(&srcs);
>     >>> +        } else {
>     >>> +            ds_put_cstr(&ctx->output, "  None attached");
>     >>> +        }
>     >>> +        ds_put_cstr(&ctx->output, "\n");
>     >>> +    }
>     >>> +
>     >>> +    free(mirrors);
>     >>> +}
>     >>> +
>     >>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>     >>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
>     >>>       = {{&nbrec_logical_switch_port_col_name, NULL,
>     >>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax
>     >>> nbctl_commands[] = {
>     >>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list,
>     nbctl_qos_list,
>     >>>         NULL, "", RO },
>     >>>
>     >>> +    /* mirror commands. */
>     >>> +    { "mirror-add", 5, 5,
>     >>> +      "NAME TYPE INDEX FILTER IP",
>     >>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL,
>     "--may-exist",
>     >>> RW },
>     >>> +    { "mirror-del", 0, 1, "[NAME]",
>     >>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>     >>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list,
>     >>> nbctl_mirror_list,
>     >>> +      NULL, "", RO },
>     >>> +
>     >>>       /* meter commands. */
>     >>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
>     >>> nbctl_pre_meter_add,
>     >>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>     >>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax
>     >>> nbctl_commands[] = {
>     >>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>     >>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
>     >>> nbctl_lsp_get_ls,
>     >>>         NULL, "", RO },
>     >>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR",
>     nbctl_pre_lsp_mirror,
>     >>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
>     >>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR",
>     nbctl_pre_lsp_mirror,
>     >>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>     >>>
>     >>>       /* forwarding group commands. */
>     >>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC
>     PORT...",
>     >>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>     >>> index f60dde1b6..3d73e9e25 100644
>     >>> --- a/utilities/ovn-sbctl.c
>     >>> +++ b/utilities/ovn-sbctl.c
>     >>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>     >>>       ovsdb_idl_add_column(ctx->idl,
>     &sbrec_port_binding_col_chassis);
>     >>>       ovsdb_idl_add_column(ctx->idl,
>     &sbrec_port_binding_col_datapath);
>     >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>     >>> +    ovsdb_idl_add_column(ctx->idl,
>     >>> &sbrec_port_binding_col_mirror_rules);
>     >>>
>     >>>       ovsdb_idl_add_column(ctx->idl,
>     >>> &sbrec_logical_flow_col_logical_datapath);
>     >>>       ovsdb_idl_add_column(ctx->idl,
>     >>> &sbrec_logical_flow_col_logical_dp_group);
>     >>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class
>     >>> tables[SBREC_N_TABLES] = {
>     >>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>     >>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>     >>>
>     >>> +    [SBREC_TABLE_MIRROR].row_ids[0]
>     >>> +    = {&sbrec_mirror_col_name, NULL, NULL},
>     >>> +
>     >>>       [SBREC_TABLE_METER].row_ids[0]
>     >>>       = {&sbrec_meter_col_name, NULL, NULL},
>     >>>
>     >>> --
>     >>> 2.31.1
>     >>>
>
Abhiram R N Nov. 16, 2022, 2:11 p.m. UTC | #6
Hi Ihar,


On Wed, Nov 16, 2022 at 7:01 PM Ihar Hrachyshka <ihrachys@redhat.com> wrote:

> On 11/16/22 8:08 AM, Abhiram R N wrote:
>
> Hi Ihar,
>
> Thanks for your inputs. I think I have found the issue in the test.
> In the test case I had missed one thing. Below are the details
>
> The log which you shared as the last message in ovs-vswitchd log led me to
> it.
>
> > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max >
> translation depth 64 on bridge br-int while processing >
> arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>
> What I had missed is, the creation of hv2, setting ip to br-phys and ARP
> resolving. (See code at the end for what I have added).
> Once I added this now everything is passing fine. I don't see any
> failures.
>
> Also everything works in a single test case. So, I will revert back to the
> old method of having 1 bulk updates test case and verify all cases there
> and with below code, will submit a patch soon.
>
> Since in the bulk updates test case we were not verifying the actual
> packet flow (already covered in a separate test) I had thought the below
> code was not needed.
> But that wasn't the case!
>
> FYI, what I added now...
>
> net_add n2
>
> sim_add hv2
> as hv2
> ovs-vsctl add-br br-phys -- set bridge br-phys
> other-config:hwaddr=\"00:00:00:02:02:00\"
> ovn_attach n2 br-phys 192.168.1.12
>
> OVN_POPULATE_ARP
>
>
> While this change to the test case may fix the test failure, I don't think
> we should just ignore the fact that any test case creating a number of
> mirrors in ovsdb may put vswitchd into a 100% cpu spin loop that
> effectively freezes all processing. We'll need to understand what happens
> to vswitchd with the original test case.
>
>
> Yeah sure, we can wait for ILya's response.
But shall go ahead and push the ovn.at change? Because that is a valid
change and required anyways I feel. And will clean up unnecessary multiple
test cases as we can just have one for all bulk update cases. Please let me
know.

In Addition to the scenario you have mentioned earlier, I got based on the
failing test case below questions
More specifically this issue happens when multiple mirrors are created with
an output port and that output port has a remote_ip set which is
non-existent . (possibly is it a wrong configuration?). (What is the
expected behaviour?)
This probably should be the same even today on OVS. I would say it has
nothing to do with configuring from OVN!.

Thanks & Regards,
Abhiram R N

>
> Thanks & Regards,
> Abhiram R N
>
> On Wed, Nov 16, 2022 at 4:39 AM Ihar Hrachyshka <ihrachys@redhat.com>
> wrote:
>
>> On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
>> > I think there's a problem with the bulk tests added in this patch. I
>> > will cover this issue in this email, and I'll send my code review
>> > tomorrow as promised, since it's getting late here.
>> >
>> >
>> > When running the whole suite locally, I get the following failures:
>> >
>> >
>> > 401: Mirror test bulk swap attachments -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425)
>> > 402: Mirror test bulk swap attachments -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425)
>> > 403: Mirror test bulk attach multiple -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537)
>> > 410: Mirror test bulk more detach and less attach -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=no ok
>> > 412: Mirror test bulk attach more than detach -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=no ok
>> > 416: Mirror test bulk detach multiple -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=no ok
>> > 408: Mirror test bulk more detach and less attach -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650)
>> > 417: Mirror test bulk detach multiple -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=yes ok
>> > 409: Mirror test bulk more detach and less attach -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650)
>> > 411: Mirror test bulk attach more than detach -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766)
>> > 418: Mirror test bulk detach multiple -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=no ok
>> > 413: Mirror test bulk attach more than detach -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766)
>> > 415: Mirror test bulk detach multiple -- ovn-northd --
>> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879)
>> > 414: Mirror test bulk attach more than detach -- ovn-northd --
>> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766)
>> >
>> >
>> > Note that some of the test case variants passed, and I don't think
>> > there's a clear pattern as to which of variants in the test matrix do
>> > fail.
>> >
>> >
>> > The error that triggers the failure is during ovs-vswitchd cleanup:
>> >
>> >
>> > ./ovn.at:16425: ovs-appctl --timeout=10 -t ovs-vswitchd exit --cleanup
>> > --- /dev/null   2022-11-04 04:09:25.869645998 +0000
>> > +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr
>> > 2022-11-15 16:31:15.557479369 +0000
>> > @@ -0,0 +1,2 @@
>> > +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with signal
>> > 14 (Alarm clock)
>> > +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source: line
>> > 282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t
>> > ovs-vswitchd exit --cleanup
>> > ./ovn.at:16425: exit code was 142, expected 0
>> >
>> >
>> > The very last message in ovs-vswitchd log on hv1 is exactly 10 seconds
>> > before the alarm clock error:
>> >
>> >
>> > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max
>> > translation depth 64 on bridge br-int while processing
>> >
>> arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>> >
>> >
>> > I don't see coredumps generated for any of test processes, so it's
>> > probably not the case of ovs-vswitchd crashing on exit request.
>> >
>> >
>> > I tried to adjust your test cases to a minimal reproducer and I found
>> > that if a test case creates two mirrors, both of to-lport type, then
>> > ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl
>> > requests, nor it handles new ports. But if I merely change the type of
>> > one of mirrors in the test to from-lport, the test passes.
>> >
>> >
>> > On the other hand, a consistent way to trigger the failure is adding a
>> > 'sleep 3' at the end of a test case just before cleanup, apparently to
>> > allow vswitchd to catch on the mirror updates and lock somewhere in
>> > the code. I see vswitchd spinning at ~100% cpu in 'top' output when it
>> > gets into this state. It's clearly doing SOMETHING, not just sleeping.
>> :)
>> >
>> >
>> > I suspect there's some bug inside vswitchd that makes it lock / spin
>> > for a particular setup of mirrors. Whatever OVN sets up in vswitchd
>> > database, the latter should not freeze. It would be helpful to provide
>> > a short ovs-only reproducer for the situation that would not involve
>> > OVN so that our OVS friends can take a look.
>> >
>> >
>> > For the record, the mirrors in ovsdb are:
>> >
>> >
>> > _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
>> > external_ids        : {}
>> > name                : mirror0
>> > output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
>> > output_vlan         : []
>> > select_all          : false
>> > select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab,
>> > 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
>> > select_src_port     : []
>> > select_vlan         : []
>> > snaplen             : []
>> > statistics          : {}
>> >
>> > _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
>> > external_ids        : {}
>> > name                : mirror1
>> > output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
>> > output_vlan         : []
>> > select_all          : false
>> > select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6,
>> > 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
>> > select_src_port     : []
>> > select_vlan         : []
>> > snaplen             : []
>> > statistics          : {}
>> >
>> >
>> > Bridge output here:
>> >
>> >
>> > 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
>> >     Bridge br-int
>> >         fail_mode: secure
>> >         datapath_type: system
>> >         Port vif4
>> >             Interface vif4
>> >         Port vif2
>> >             Interface vif2
>> >         Port br-int
>> >             Interface br-int
>> >                 type: internal
>> >         Port vif1
>> >             Interface vif1
>> >         Port ovn-mirror0
>> >             Interface ovn-mirror0
>> >                 type: gre
>> >                 options: {key="0", remote_ip="192.168.1.12"}
>> >         Port vif3
>> >             Interface vif3
>> >         Port ovn-mirror1
>> >             Interface ovn-mirror1
>> >                 type: gre
>> >                 options: {key="1", remote_ip="192.168.1.12"}
>> >         Port patch-br-int-to-ln-public
>> >             Interface patch-br-int-to-ln-public
>> >                 type: patch
>> >                 options: {peer=patch-ln-public-to-br-int}
>> >     Bridge br-phys
>> >         Port br-phys
>> >             Interface br-phys
>> >                 type: internal
>> >                 options:
>> >
>> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap",
>> >
>> tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
>> >         Port br-phys_n1
>> >             Interface br-phys_n1
>> >                 options:
>> >
>> {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap",
>>
>> >
>> stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock",
>>
>> >
>> tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
>>
>> >
>> >         Port patch-ln-public-to-br-int
>> >             Interface patch-ln-public-to-br-int
>> >                 type: patch
>> >                 options: {peer=patch-br-int-to-ln-public}
>> >
>> Perhaps this may be of relevance: when I attach gdb to vswitchd process,
>> I can see a never-ending stack of calls in thread 1 that looks like:
>>
>> #0  0x0000000000406760 in memcpy@plt ()
>> #1  0x0000000000499d88 in dp_packet_clone_with_headroom
>> (buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
>> #2  0x000000000049bd4d in dp_packet_batch_clone (dst=0x7fffcbf54740,
>> src=0x7fffcbf55230) at lib/dp-packet.h:863
>> #3  0x00000000004b15ef in dp_execute_output_action (pmd=0x7f638115f010,
>> packets_=0x7fffcbf55230, should_steal=false, port_no=3) at
>> lib/dpif-netdev.c:8696
>> #4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200,
>> packets_=0x7fffcbf55230, a=0x7fffcbf555c0, should_steal=false) at
>> lib/dpif-netdev.c:8787
>> #5  0x0000000000507834 in odp_execute_actions (dp=0x7fffcbf55200,
>> batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578,
>> actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
>>      at lib/odp-execute.c:993
>> #6  0x00000000004b251e in dp_netdev_execute_actions (pmd=0x7f638115f010,
>> packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60,
>> actions=0x7fffcbf55578, actions_len=88)
>>      at lib/dpif-netdev.c:9105
>> #7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630,
>> execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
>> #8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630,
>> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>> lib/dpif-netdev.c:4606
>> #9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630,
>> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>> lib/dpif.c:1372
>> #10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630,
>> execute=0x7fffcbf55500) at lib/dpif.c:1326
>> #11 0x000000000043e46d in ofproto_dpif_execute_actions__
>> (ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0,
>> ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
>>      packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
>> #12 0x0000000000467e8a in compose_table_xlate (ctx=0x7fffcbf5f240,
>> out_dev=0x1879430, packet=0x7fffcbf56050) at
>> ofproto/ofproto-dpif-xlate.c:3526
>> #13 0x0000000000468020 in tnl_send_arp_request (ctx=0x7fffcbf5f240,
>> out_dev=0x1879430, eth_src=..., ip_src=184658112, ip_dst=201435328) at
>> ofproto/ofproto-dpif-xlate.c:3555
>> #14 0x0000000000468710 in native_tunnel_output (ctx=0x7fffcbf5f240,
>> xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7, truncate=false,
>> is_last_action=false)
>>      at ofproto/ofproto-dpif-xlate.c:3721
>> #15 0x000000000046a78e in compose_output_action__ (ctx=0x7fffcbf5f240,
>> ofp_port=7, xr=0x0, check_stp=true, is_last_action=false,
>> truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
>> #16 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
>> ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at
>> ofproto/ofproto-dpif-xlate.c:4416
>> #17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240,
>> out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at
>> ofproto/ofproto-dpif-xlate.c:2533
>> #18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240,
>> xbundle=0x1880d00, mirrors=2) at ofproto/ofproto-dpif-xlate.c:2190
>> #19 0x000000000046a96b in compose_output_action__ (ctx=0x7fffcbf5f240,
>> ofp_port=1, xr=0x0, check_stp=true, is_last_action=false,
>> truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
>> #20 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
>> ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at
>> ofproto/ofproto-dpif-xlate.c:4416
>> #21 0x000000000046cf3e in xlate_output_action (ctx=0x7fffcbf5f240,
>> port=1, controller_len=0, may_packet_in=true, is_last_action=false,
>> truncate=false, group_bucket_action=false)
>>      at ofproto/ofproto-dpif-xlate.c:5361
>> #22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638,
>> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
>> #23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>> rule=0x1860480, deepens=false, is_last_action=false,
>> actions_xlator=0x470caf <do_xlate_actions>)
>>      at ofproto/ofproto-dpif-xlate.c:4439
>> #24 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
>> in_port=5, table_id=65 'A', may_packet_in=false, honor_table_miss=false,
>> with_ct_orig=false, is_last_action=false,
>>      xlator=0x470caf <do_xlate_actions>) at
>> ofproto/ofproto-dpif-xlate.c:4568
>> #25 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
>> resubmit=0x185cfb8, is_last_action=false) at
>> ofproto/ofproto-dpif-xlate.c:4879
>> #26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8,
>> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>> #27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>> rule=0x185ce00, deepens=false, is_last_action=false,
>> actions_xlator=0x470caf <do_xlate_actions>)
>>      at ofproto/ofproto-dpif-xlate.c:4439
>> #28 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
>> in_port=5, table_id=64 '@', may_packet_in=false, honor_table_miss=false,
>> with_ct_orig=false, is_last_action=false,
>>      xlator=0x470caf <do_xlate_actions>) at
>> ofproto/ofproto-dpif-xlate.c:4568
>> #29 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
>> resubmit=0x1834458, is_last_action=false) at
>> ofproto/ofproto-dpif-xlate.c:4879
>> #30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458,
>> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>>
>> it goes like that over and over and over for hundreds if not thousands
>> of calls down the stack until
>>
>> #5738 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
>> in_port=65533, table_id=9 '\t', may_packet_in=false,
>> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
>> #5739 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
>> resubmit=0x1831ac0, is_last_action=true) at
>> ofproto/ofproto-dpif-xlate.c:4879
>> #5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68,
>> ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true,
>> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>> #5741 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcc09b330,
>> rule=0x18318b0, deepens=false, is_last_action=true,
>> actions_xlator=0x470caf <do_xlate_actions>) at
>> ofproto/ofproto-dpif-xlate.c:4439
>> #5742 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
>> in_port=65533, table_id=8 '\b', may_packet_in=false,
>> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
>> #5743 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
>> resubmit=0x187bcc0, is_last_action=true) at
>> ofproto/ofproto-dpif-xlate.c:4879
>> #5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80,
>> ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true,
>> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>> #5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0,
>> xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
>> #5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0,
>> opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
>> #5747 0x00000000004250d7 in ofproto_packet_out_start (ofproto=0x17c10e0,
>> opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
>> #5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40,
>> oh=0x17fdfa0) at ofproto/ofproto.c:3764
>> #5749 0x000000000042fce2 in handle_single_part_openflow
>> (ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at
>> ofproto/ofproto.c:8664
>> #5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40,
>> msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
>> #5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40,
>> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:1329
>> #5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0,
>> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:356
>> #5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at
>> ofproto/ofproto.c:1933
>> #5754 0x00000000004101d4 in bridge_run__ () at vswitchd/bridge.c:3210
>> #5755 0x00000000004103d3 in bridge_run () at vswitchd/bridge.c:3269
>> #5756 0x0000000000415cf0 in main (argc=10, argv=0x7fffcc09dff8) at
>> vswitchd/ovs-vswitchd.c:129
>>
>> The main thread never getting out of some processing code to reach any
>> other handlers (e.g. for appctl requests?)
>>
>> I'm adding Ilya to CC in case he has an idea why vswitchd could lock /
>> freeze / spin indefinitely on two to-lport mirror creation.
>>
>>
>> > On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
>> >> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N <abhiramrn@gmail.com>
>> wrote:
>> >>> Mirror creation just creates the mirror. The lsp-attach-mirror
>> >>> triggers the sequence to create Mirror in OVS DB on compute node.
>> >>> OVS already supports Port Mirroring.
>> >>>
>> >>> Note: This is targeted to mirror to destinations anywhere outside the
>> >>> cluster where the analyser resides and it need not be an OVN node.
>> >>>
>> >>> Example commands are as below:
>> >>>
>> >>> Mirror creation
>> >>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>> >>>
>> >>> Attach a logical port to the mirror.
>> >>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>> >>>
>> >>> Detach a source from Mirror
>> >>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>> >>>
>> >>> Mirror deletion
>> >>> ovn-nbctl mirror-del mirror1
>> >>>
>> >>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>> >>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>> >>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
>> >>> ---
>> >>> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
>> >>>               test to make it pass consistently.
>> >>> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
>> >>>
>> >>> V10 --> V11: Addressed review comments from V10 by Ihar
>> >>>             i) Expanded bulk updates test cases in ovn.at
>> >>>                Overall below cases are covered
>> >>>                a) Attaches multiple mirrors (new)
>> >>>                b) Equal detaches and attaches (same as V10)
>> >>>                c) Detaches more than attaches (new)
>> >>>                d) Attaches more than detaches (new)
>> >>>                e) Detaches all (new)
>> >>>            ii) Addressed the detach all case in mirror.c
>> >>>           iii) Minor correction in NEWS
>> >>>            iv) Added invalid mirror attach case in ovn-nbctl.at
>> >>>
>> >>> Files modified (V10 --> V11):
>> >>> Code --> mirror.c
>> >>> Test --> ovn.at, ovn-nbctl.at
>> >>> Misc --> NEWS
>> >>>
>> >>> Ihar,
>> >>>      Regarding mirror_delete function param delete_all it is wrt the
>> >>> port binding and if a port binding is removed we delete all its
>> >>> attachment. Already that use case is covered in ovn.at.
>> >>> Having said that the detaches all had issue in mirror_delete which
>> >>> I have addressed. With all the above cases added now in bulk updates
>> >>> hope it should give good assurance.
>> >>>
>> >>>   NEWS                        |   1 +
>> >>>   controller/automake.mk      |   4 +-
>> >>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>> >>>   controller/mirror.h         |  53 +++
>> >>>   controller/ovn-controller.c | 266 ++++++++++--
>> >>>   northd/en-northd.c          |   4 +
>> >>>   northd/inc-proc-northd.c    |   4 +
>> >>>   northd/northd.c             | 172 ++++++++
>> >>>   northd/northd.h             |   2 +
>> >>>   ovn-nb.ovsschema            |  31 +-
>> >>>   ovn-nb.xml                  |  63 +++
>> >>>   ovn-sb.ovsschema            |  26 +-
>> >>>   ovn-sb.xml                  |  50 +++
>> >>>   tests/ovn-nbctl.at          | 120 ++++++
>> >>>   tests/ovn-northd.at         | 102 +++++
>> >>>   tests/ovn.at                | 778
>> >>> ++++++++++++++++++++++++++++++++++++
>> >>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>> >>>   utilities/ovn-sbctl.c       |   4 +
>> >>>   18 files changed, 2547 insertions(+), 28 deletions(-)
>> >>>   create mode 100644 controller/mirror.c
>> >>>   create mode 100644 controller/mirror.h
>> >>>
>> >>> diff --git a/NEWS b/NEWS
>> >>> index 224a7b83e..84b22abdb 100644
>> >>> --- a/NEWS
>> >>> +++ b/NEWS
>> >>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>> >>>       any of LR's LRP IP, there is no need to create SNAT entry.
>> >>> Now such
>> >>>       traffic destined to LRP IP is not dropped.
>> >>>     - Bump python version required for building OVN to 3.6.
>> >>> +  - Added Support for Remote Port Mirroring.
>> >>>
>> >>>   OVN v22.06.0 - 03 Jun 2022
>> >>>   --------------------------
>> >>> diff --git a/controller/automake.mk b/controller/automake.mk
>> >>> index c2ab1bbe6..334672b4d 100644
>> >>> --- a/controller/automake.mk
>> >>> +++ b/controller/automake.mk
>> >>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>> >>>          controller/ovsport.h \
>> >>>          controller/ovsport.c \
>> >>>          controller/vif-plug.h \
>> >>> -       controller/vif-plug.c
>> >>> +       controller/vif-plug.c \
>> >>> +       controller/mirror.h \
>> >>> +       controller/mirror.c
>> >>>
>> >>>   controller_ovn_controller_LDADD = lib/libovn.la
>> >>> $(OVS_LIBDIR)/libopenvswitch.la
>> >>>   man_MANS += controller/ovn-controller.8
>> >>> diff --git a/controller/mirror.c b/controller/mirror.c
>> >>> new file mode 100644
>> >>> index 000000000..11f2b63a6
>> >>> --- /dev/null
>> >>> +++ b/controller/mirror.c
>> >>> @@ -0,0 +1,538 @@
>> >>> +/* Copyright (c) 2022 Red Hat, Inc.
>> >>> + *
>> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
>> >>> + * you may not use this file except in compliance with the License.
>> >>> + * You may obtain a copy of the License at:
>> >>> + *
>> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
>> >>> + *
>> >>> + * Unless required by applicable law or agreed to in writing,
>> software
>> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
>> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>> >>> implied.
>> >>> + * See the License for the specific language governing permissions
>> and
>> >>> + * limitations under the License.
>> >>> + */
>> >>> +
>> >>> +#include <config.h>
>> >>> +#include <unistd.h>
>> >>> +
>> >>> +/* library headers */
>> >>> +#include "lib/sset.h"
>> >>> +#include "lib/util.h"
>> >>> +
>> >>> +/* OVS includes. */
>> >>> +#include "lib/vswitch-idl.h"
>> >>> +#include "openvswitch/vlog.h"
>> >>> +
>> >>> +/* OVN includes. */
>> >>> +#include "binding.h"
>> >>> +#include "lib/ovn-sb-idl.h"
>> >>> +#include "mirror.h"
>> >>> +
>> >>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
>> >>> +
>> >>> +/* Static function declarations */
>> >>> +
>> >>> +static const struct ovsrec_port *
>> >>> +get_port_for_iface(const struct ovsrec_interface *iface,
>> >>> +                  const struct ovsrec_bridge *br_int)
>> >>> +{
>> >>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
>> >>> +        const struct ovsrec_port *p = br_int->ports[i];
>> >>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
>> >>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
>> >>> +                return p;
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +    return NULL;
>> >>> +}
>> >>> +
>> >>> +static bool
>> >>> +mirror_create(const struct sbrec_port_binding *pb,
>> >>> +              struct port_mirror_ctx *pm_ctx)
>> >>> +{
>> >>> +    const struct ovsrec_mirror *mirror = NULL;
>> >>> +
>> >>> +    if (pb->n_up && !pb->up[0]) {
>> >>> +        return true;
>> >>> +    }
>> >>> +
>> >>> +    if (pb->chassis != pm_ctx->chassis_rec) {
>> >>> +        return true;
>> >>> +    }
>> >>> +
>> >>> +    if (!pm_ctx->ovs_idl_txn) {
>> >>> +        return false;
>> >>> +    }
>> >>> +
>> >>> +
>> >>> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>> >>> +    /* Loop through the mirror rules */
>> >>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>> >>> +        /* check if the mirror already exists in OVS DB */
>> >>> +        bool create_mirror = true;
>> >>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> >>> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>> >>> +                /* Mirror with same name already exists
>> >>> +                 * No need to create mirror
>> >>> +                 */
>> >>> +                create_mirror = false;
>> >>> +                break;
>> >>> +            }
>> >>> +        }
>> >>> +
>> >>> +        if (create_mirror) {
>> >>> +
>> >>> +            struct smap options = SMAP_INITIALIZER(&options);
>> >>> +            char *port_name, *key;
>> >>> +
>> >>> +            key = xasprintf("%ld",(long int)
>> >>> pb->mirror_rules[i]->index);
>> >>> +            smap_add(&options, "remote_ip",
>> >>> pb->mirror_rules[i]->sink);
>> >>> +            smap_add(&options, "key", key);
>> >>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>> >>> +                /* Set the ERSPAN index */
>> >>> +                smap_add(&options, "erspan_idx", key);
>> >>> +                smap_add(&options, "erspan_ver","1");
>> >>> +
>> >>> +            }
>> >>> +            struct ovsrec_interface *iface =
>> >>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>> >>> +            port_name = xasprintf("ovn-%s",
>> >>> + pb->mirror_rules[i]->name);
>> >>> +
>> >>> +            ovsrec_interface_set_name(iface, port_name);
>> >>> +            ovsrec_interface_set_type(iface,
>> >>> pb->mirror_rules[i]->type);
>> >>> +            ovsrec_interface_set_options(iface, &options);
>> >>> +
>> >>> +            struct ovsrec_port *port =
>> >>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>> >>> +            ovsrec_port_set_name(port, port_name);
>> >>> +            ovsrec_port_set_interfaces(port, &iface, 1);
>> >>> +
>> >>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>> >>> +
>> >>> +            smap_destroy(&options);
>> >>> +            free(port_name);
>> >>> +            free(key);
>> >>> +
>> >>> +            VLOG_INFO("Creating Mirror in OVS DB");
>> >>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>> >>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>> >>> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
>> >>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>> >>> + mirror);
>> >>> +        }
>> >>> +
>> >>> +        struct local_binding *lbinding = local_binding_find(
>> >>> +                               pm_ctx->local_bindings,
>> >>> pb->logical_port);
>> >>> +        const struct ovsrec_port *p =
>> >>> +                     get_port_for_iface(lbinding->iface,
>> >>> pm_ctx->br_int);
>> >>> +        if (p) {
>> >>> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> >>> +            } else if
>> >>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> >>> +            } else {
>> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +    return true;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
>> >>> +                              struct ovsrec_mirror *ovs_mirror)
>> >>> +{
>> >>> +    char *filter;
>> >>> +    if ((ovs_mirror->n_select_dst_port)
>> >>> +            && (ovs_mirror->n_select_src_port)) {
>> >>> +        filter = "both";
>> >>> +    } else if (ovs_mirror->n_select_dst_port) {
>> >>> +        filter = "to-lport";
>> >>> +    } else {
>> >>> +        filter = "from-lport";
>> >>> +    }
>> >>> +
>> >>> +    if (strcmp(sb_mirror->filter, filter)) {
>> >>> +        if (!strcmp(sb_mirror->filter,"from-lport")
>> >>> +                              && !strcmp(filter,"both")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> >>> + ovs_mirror->select_dst_port[i]);
>> >>> +            }
>> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> >>> +                              && !strcmp(filter,"both")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> >>> + ovs_mirror->select_src_port[i]);
>> >>> +            }
>> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>> >>> +                              && !strcmp(filter,"from-lport")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> >>> + ovs_mirror->select_src_port[i]);
>> >>> +            }
>> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>> >>> +                              && !strcmp(filter,"to-lport")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> >>> + ovs_mirror->select_dst_port[i]);
>> >>> +            }
>> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> >>> +                              && !strcmp(filter,"from-lport")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> >>> + ovs_mirror->select_src_port[i]);
>> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> >>> + ovs_mirror->select_src_port[i]);
>> >>> +            }
>> >>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>> >>> +                              && !strcmp(filter,"to-lport")) {
>> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
>> >>> i++) {
>> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> >>> + ovs_mirror->select_dst_port[i]);
>> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> >>> + ovs_mirror->select_dst_port[i]);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +check_and_update_interface_table(const struct sbrec_mirror
>> *sb_mirror,
>> >>> +                                   struct ovsrec_mirror *ovs_mirror)
>> >>> +{
>> >>> +    struct smap options = SMAP_INITIALIZER(&options);
>> >>> +    char *key, *type;
>> >>> +    struct ovsrec_interface *iface =
>> >>> + ovs_mirror->output_port->interfaces[0];
>> >>> +    struct smap *opts = &iface->options;
>> >>> +
>> >>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>> >>> +    if (erspan_ver) {
>> >>> +        type = "erspan";
>> >>> +    } else {
>> >>> +        type = "gre";
>> >>> +    }
>> >>> +    if (strcmp(type, sb_mirror->type)) {
>> >>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
>> >>> +    }
>> >>> +
>> >>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>> >>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
>> >>> +    smap_add(&options, "key", key);
>> >>> +
>> >>> +    if (!strcmp(sb_mirror->type, "erspan")) {
>> >>> +        /* Set the ERSPAN index */
>> >>> +        smap_add(&options, "erspan_idx", key);
>> >>> +        smap_add(&options, "erspan_ver","1");
>> >>> +    }
>> >>> +
>> >>> +    ovsrec_interface_set_options(iface, &options);
>> >>> +    smap_destroy(&options);
>> >>> +    free(key);
>> >>> +
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +mirror_update(const struct sbrec_mirror *sb_mirror,
>> >>> +              struct ovsrec_mirror *ovs_mirror)
>> >>> +{
>> >>> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
>> >>> +
>> >>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>> >>> +}
>> >>> +
>> >>> +static bool
>> >>> +mirror_delete(const struct sbrec_port_binding *pb,
>> >>> +              struct port_mirror_ctx *pm_ctx,
>> >>> +              struct shash *pb_mirror_map,
>> >>> +              bool delete_all)
>> >>> +{
>> >>> +
>> >>> +    if (!pm_ctx->ovs_idl_txn) {
>> >>> +        return false;
>> >>> +    }
>> >>> +
>> >>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>> >>> +
>> >>> +    if (!delete_all) {
>> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> >>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
>> >>> pb->n_mirror_rules) {
>> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> >>> +
>> >>> +            struct ovsrec_mirror *ovs_mirror = NULL;
>> >>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>> >>> + pb->mirror_rules[i]->name);
>> >>> +            if (ovs_mirror) {
>> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror->output_port);
>> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror);
>> >>> + ovsrec_port_delete(ovs_mirror->output_port);
>> >>> +                ovsrec_mirror_delete(ovs_mirror);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    struct shash_node *mirror_node;
>> >>> +    const struct sbrec_port_binding *sb_pb;
>> >>> +    int attach_cnt = 0;
>> >>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>> >>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>> >>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>> >>> +            /* Find if the mirror has other sources */
>> >>> +            attach_cnt = 0;
>> >>> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>> >>> + pm_ctx->port_binding_table) {
>> >>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
>> >>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
>> >>> + ovs_mirror->name)) {
>> >>> +                        attach_cnt++;
>> >>> +                    }
>> >>> +                }
>> >>> +            }
>> >>> +            if (attach_cnt) {
>> >>> +                /* More than 1 source then just
>> >>> +                 * update the mirror table
>> >>> +                 */
>> >>> +                bool done = false;
>> >>> +                for (size_t i = 0; ((i <
>> >>> ovs_mirror->n_select_dst_port)
>> >>> +                                                   && (done ==
>> >>> false)); i++) {
>> >>> +                    const struct ovsrec_port *port_rec =
>> >>> + ovs_mirror->select_dst_port[i];
>> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
>> >>> j++) {
>> >>> +                        const struct ovsrec_interface *iface_rec;
>> >>> +
>> >>> +                        iface_rec = port_rec->interfaces[j];
>> >>> +                        const char *iface_id =
>> >>> + smap_get(&iface_rec->external_ids,
>> >>> + "iface-id");
>> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>> >>> + ovsrec_mirror_update_select_dst_port_delvalue(
>> >>> + ovs_mirror, port_rec);
>> >>> +                            done = true;
>> >>> +                            break;
>> >>> +                        }
>> >>> +                    }
>> >>> +                }
>> >>> +                done = false;
>> >>> +                for (size_t i = 0; ((i <
>> >>> ovs_mirror->n_select_src_port)
>> >>> +                                                   && (done ==
>> >>> false)); i++) {
>> >>> +                    const struct ovsrec_port *port_rec =
>> >>> + ovs_mirror->select_src_port[i];
>> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
>> >>> j++) {
>> >>> +                        const struct ovsrec_interface *iface_rec;
>> >>> +
>> >>> +                        iface_rec = port_rec->interfaces[j];
>> >>> +                        const char *iface_id =
>> >>> + smap_get(&iface_rec->external_ids,
>> >>> + "iface-id");
>> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
>> >>> + ovsrec_mirror_update_select_src_port_delvalue(
>> >>> + ovs_mirror, port_rec);
>> >>> +                            done = true;
>> >>> +                            break;
>> >>> +                        }
>> >>> +                    }
>> >>> +                }
>> >>> +            } else {
>> >>> +                /*
>> >>> +                 * If only 1 source delete the output port
>> >>> +                 * and then delete the mirror completely
>> >>> +                 */
>> >>> +                VLOG_INFO("Only 1 source for the mirror. Hence
>> >>> delete it");
>> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror->output_port);
>> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror);
>> >>> + ovsrec_port_delete(ovs_mirror->output_port);
>> >>> +                ovsrec_mirror_delete(ovs_mirror);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    const char *used_node, *used_next;
>> >>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>> >>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>> >>> +    }
>> >>> +    sset_destroy(&pb_mirrors);
>> >>> +
>> >>> +    return true;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>> >>> +                            struct port_mirror_ctx *pm_ctx,
>> >>> +                            struct shash *pb_mirror_map)
>> >>> +{
>> >>> +    const struct ovsrec_mirror *mirror = NULL;
>> >>> +
>> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> >>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>> >>> +            const struct ovsrec_port *port_rec =
>> >>> mirror->select_dst_port[i];
>> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> >>> +                const struct ovsrec_interface *iface_rec;
>> >>> +                iface_rec = port_rec->interfaces[j];
>> >>> +                const char *logical_port =
>> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
>> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
>> >>> mirror);
>> >>> +                }
>> >>> +            }
>> >>> +        }
>> >>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>> >>> +            const struct ovsrec_port *port_rec =
>> >>> mirror->select_src_port[i];
>> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> >>> +                const struct ovsrec_interface *iface_rec;
>> >>> +                iface_rec = port_rec->interfaces[j];
>> >>> +                const char *logical_port =
>> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
>> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
>> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
>> >>> mirror);
>> >>> +                }
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +void
>> >>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> >>> +{
>> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>> >>> +
>> >>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
>> >>> +    ovsdb_idl_add_column(ovs_idl,
>> &ovsrec_mirror_col_select_dst_port);
>> >>> +    ovsdb_idl_add_column(ovs_idl,
>> &ovsrec_mirror_col_select_src_port);
>> >>> +}
>> >>> +
>> >>> +
>> >>> +void
>> >>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
>> >>> +{
>> >>> +    shash_init(ovs_mirrors);
>> >>> +}
>> >>> +
>> >>> +void
>> >>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>> >>> +{
>> >>> +    const struct sbrec_port_binding *pb;
>> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>> >>> + pm_ctx->port_binding_table) {
>> >>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +bool
>> >>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
>> >>> bool removed,
>> >>> +                     struct port_mirror_ctx *pm_ctx)
>> >>> +{
>> >>> +    bool ret = true;
>> >>> +    struct local_binding *lbinding = local_binding_find(
>> >>> +                               pm_ctx->local_bindings,
>> >>> pb->logical_port);
>> >>> +
>> >>> +    if (strcmp(pb->type, "") && (!lbinding)) {
>> >>> +        return ret;
>> >>> +    }
>> >>> +
>> >>> +    struct shash port_ovs_mirrors =
>> >>> SHASH_INITIALIZER(&port_ovs_mirrors);
>> >>> +
>> >>> +    /* Need to find if mirror needs update */
>> >>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>> >>> +    if (!removed) {
>> >>> +        if ((pb->n_mirror_rules == 0)
>> >>> +              && (shash_is_empty(&port_ovs_mirrors))) {
>> >>> +            /* No mirror update */
>> >>> +        } else if (pb->n_mirror_rules ==
>> >>> shash_count(&port_ovs_mirrors)) {
>> >>> +            /* Though number of mirror rules are same,
>> >>> +             * need to verify the contents
>> >>> +             */
>> >>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
>> >>> +                if (!shash_find(&port_ovs_mirrors,
>> >>> + pb->mirror_rules[i]->name)) {
>> >>> +                    /* Mis match in OVN SB DB and OVS DB
>> >>> +                     * Delete and Create mirror(s) with proper
>> sources
>> >>> +                     */
>> >>> +                    ret = mirror_delete(pb, pm_ctx,
>> >>> + &port_ovs_mirrors, false);
>> >>> +                    if (ret) {
>> >>> +                        ret = mirror_create(pb, pm_ctx);
>> >>> +                    }
>> >>> +                    break;
>> >>> +                }
>> >>> +            }
>> >>> +        } else {
>> >>> +            /* Update Mirror */
>> >>> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors))
>> {
>> >>> +                /* create mirror,
>> >>> +                 * if mirror already exists only update selection
>> >>> +                 */
>> >>> +                ret = mirror_create(pb, pm_ctx);
>> >>> +            } else {
>> >>> +                /* delete mirror,
>> >>> +                 * if mirror has other sources only update selection
>> >>> +                 */
>> >>> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors,
>> >>> false);
>> >>> +            }
>> >>> +        }
>> >>> +    } else {
>> >>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>> >>> +    }
>> >>> +
>> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> >>> + &port_ovs_mirrors) {
>> >>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>> >>> +    }
>> >>> +    shash_destroy(&port_ovs_mirrors);
>> >>> +
>> >>> +    return ret;
>> >>> +}
>> >>> +
>> >>> +bool
>> >>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>> >>> +{
>> >>> +    const struct sbrec_mirror *mirror = NULL;
>> >>> +    struct ovsrec_mirror *ovs_mirror = NULL;
>> >>> +
>> >>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
>> >>> pm_ctx->sb_mirror_table) {
>> >>> +    /* For each tracked mirror entry check if OVS entry is there*/
>> >>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>> >>> mirror->name);
>> >>> +        if (ovs_mirror) {
>> >>> +            if (sbrec_mirror_is_deleted(mirror)) {
>> >>> +                /* Need to delete the mirror in OVS */
>> >>> +                VLOG_INFO("Delete mirror and remove port");
>> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror->output_port);
>> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> >>> + ovs_mirror);
>> >>> + ovsrec_port_delete(ovs_mirror->output_port);
>> >>> +                ovsrec_mirror_delete(ovs_mirror);
>> >>> +            } else {
>> >>> +                mirror_update(mirror, ovs_mirror);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    return true;
>> >>> +}
>> >>> +
>> >>> +void
>> >>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>> >>> +{
>> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> >>> +                                              ovs_mirrors) {
>> >>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
>> >>> +    }
>> >>> +    shash_destroy(ovs_mirrors);
>> >>> +}
>> >>> diff --git a/controller/mirror.h b/controller/mirror.h
>> >>> new file mode 100644
>> >>> index 000000000..85b964f55
>> >>> --- /dev/null
>> >>> +++ b/controller/mirror.h
>> >>> @@ -0,0 +1,53 @@
>> >>> +/* Copyright (c) 2022 Red Hat, Inc.
>> >>> + *
>> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
>> >>> + * you may not use this file except in compliance with the License.
>> >>> + * You may obtain a copy of the License at:
>> >>> + *
>> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
>> >>> + *
>> >>> + * Unless required by applicable law or agreed to in writing,
>> software
>> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
>> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>> >>> implied.
>> >>> + * See the License for the specific language governing permissions
>> and
>> >>> + * limitations under the License.
>> >>> + */
>> >>> +
>> >>> +#ifndef OVN_MIRROR_H
>> >>> +#define OVN_MIRROR_H 1
>> >>> +
>> >>> +struct ovsdb_idl_txn;
>> >>> +struct ovsrec_port_table;
>> >>> +struct ovsrec_bridge;
>> >>> +struct ovsrec_bridge_table;
>> >>> +struct ovsrec_open_vswitch_table;
>> >>> +struct sbrec_chassis;
>> >>> +struct ovsrec_interface_table;
>> >>> +struct ovsrec_mirror_table;
>> >>> +struct sbrec_mirror_table;
>> >>> +struct sbrec_port_binding_table;
>> >>> +
>> >>> +struct port_mirror_ctx {
>> >>> +    struct shash *ovs_mirrors;
>> >>> +    struct ovsdb_idl_txn *ovs_idl_txn;
>> >>> +    const struct ovsrec_port_table *port_table;
>> >>> +    const struct ovsrec_bridge *br_int;
>> >>> +    const struct sbrec_chassis *chassis_rec;
>> >>> +    const struct ovsrec_bridge_table *bridge_table;
>> >>> +    const struct ovsrec_open_vswitch_table *ovs_table;
>> >>> +    const struct ovsrec_interface_table *iface_table;
>> >>> +    const struct ovsrec_mirror_table *mirror_table;
>> >>> +    const struct sbrec_mirror_table *sb_mirror_table;
>> >>> +    const struct sbrec_port_binding_table *port_binding_table;
>> >>> +    struct shash *local_bindings;
>> >>> +};
>> >>> +
>> >>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
>> >>> +void ovn_port_mirror_init(struct shash *);
>> >>> +void ovn_port_mirror_destroy(struct shash *);
>> >>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>> >>> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding
>> *pb,
>> >>> +                                  bool removed,
>> >>> +                                  struct port_mirror_ctx *pm_ctx);
>> >>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
>> >>> +#endif
>> >>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>> >>> index 8895c7a2b..15ab17c4a 100644
>> >>> --- a/controller/ovn-controller.c
>> >>> +++ b/controller/ovn-controller.c
>> >>> @@ -78,6 +78,7 @@
>> >>>   #include "lib/inc-proc-eng.h"
>> >>>   #include "lib/ovn-l7.h"
>> >>>   #include "hmapx.h"
>> >>> +#include "mirror.h"
>> >>>
>> >>>   VLOG_DEFINE_THIS_MODULE(main);
>> >>>
>> >>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>> >>>       ovsdb_idl_track_add_column(ovs_idl,
>> &ovsrec_port_col_interfaces);
>> >>>       ovsdb_idl_track_add_column(ovs_idl,
>> >>> &ovsrec_port_col_external_ids);
>> >>> +    mirror_register_ovs_idl(ovs_idl);
>> >>>   }
>> >>>
>> >>>   #define SB_NODES \
>> >>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> >>>       SB_NODE(load_balancer, "load_balancer") \
>> >>>       SB_NODE(fdb, "fdb") \
>> >>>       SB_NODE(meter, "meter") \
>> >>> +    SB_NODE(mirror, "mirror") \
>> >>>       SB_NODE(static_mac_binding, "static_mac_binding")
>> >>>
>> >>>   enum sb_engine_node {
>> >>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>> >>>       OVS_NODE(bridge, "bridge") \
>> >>>       OVS_NODE(port, "port") \
>> >>>       OVS_NODE(interface, "interface") \
>> >>> -    OVS_NODE(qos, "qos")
>> >>> +    OVS_NODE(qos, "qos") \
>> >>> +    OVS_NODE(mirror, "mirror")
>> >>>
>> >>>   enum ovs_engine_node {
>> >>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>> >>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>> >>>       free(lbs);
>> >>>   }
>> >>>
>> >>> +/* Mirror Engine */
>> >>> +struct ed_type_port_mirror {
>> >>> +    struct shash ovs_mirrors;
>> >>> +};
>> >>> +
>> >>> +static void *
>> >>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>> >>> +                    struct engine_arg *arg OVS_UNUSED)
>> >>> +{
>> >>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof
>> >>> *port_mirror);
>> >>> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>> >>> +    return port_mirror;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +en_port_mirror_cleanup(void *data)
>> >>> +{
>> >>> +    struct ed_type_port_mirror *port_mirror = data;
>> >>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +init_port_mirror_ctx(struct engine_node *node,
>> >>> +                 struct ed_type_runtime_data *rt_data,
>> >>> +                 struct ed_type_port_mirror *port_mirror_data,
>> >>> +                 struct port_mirror_ctx *pm_ctx)
>> >>> +{
>> >>> +    struct ovsrec_open_vswitch_table *ovs_table =
>> >>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("OVS_open_vswitch", node));
>> >>> +    struct ovsrec_bridge_table *bridge_table =
>> >>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("OVS_bridge", node));
>> >>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>> >>> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
>> >>> ovs_table);
>> >>> +
>> >>> +    ovs_assert(br_int && chassis_id);
>> >>> +    const struct sbrec_chassis *chassis = NULL;
>> >>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>> >>> +        engine_ovsdb_node_get_index(
>> >>> +                engine_get_input("SB_chassis", node),
>> >>> +                "name");
>> >>> +
>> >>> +    if (chassis_id) {
>> >>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name,
>> >>> chassis_id);
>> >>> +    }
>> >>> +    ovs_assert(chassis);
>> >>> +
>> >>> +    struct ovsrec_port_table *port_table =
>> >>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("OVS_port", node));
>> >>> +
>> >>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
>> >>> +        engine_get_input_data("ovs_interface_shadow", node);
>> >>> +
>> >>> +    struct ovsrec_mirror_table *mirror_table =
>> >>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("OVS_mirror", node));
>> >>> +
>> >>> +    struct sbrec_port_binding_table *pb_table =
>> >>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("SB_port_binding", node));
>> >>> +
>> >>> +    struct sbrec_mirror_table *sb_mirror_table =
>> >>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>> >>> +            engine_get_input("SB_mirror", node));
>> >>> +
>> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> >>> + &port_mirror_data->ovs_mirrors) {
>> >>> +        shash_delete(&port_mirror_data->ovs_mirrors,
>> ovs_mirror_node);
>> >>> +    }
>> >>> +
>> >>> +    const struct ovsrec_mirror *ovsmirror = NULL;
>> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>> >>> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
>> >>> ovsmirror);
>> >>> +    }
>> >>> +
>> >>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>> >>> +    pm_ctx->port_table = port_table;
>> >>> +    pm_ctx->iface_table = iface_shadow->iface_table;
>> >>> +    pm_ctx->mirror_table = mirror_table;
>> >>> +    pm_ctx->port_binding_table = pb_table;
>> >>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
>> >>> +    pm_ctx->br_int = br_int;
>> >>> +    pm_ctx->chassis_rec = chassis;
>> >>> +    pm_ctx->bridge_table = bridge_table;
>> >>> +    pm_ctx->ovs_table = ovs_table;
>> >>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>> >>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +en_port_mirror_run(struct engine_node *node, void *data)
>> >>> +{
>> >>> +    struct port_mirror_ctx pm_ctx;
>> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> >>> +    struct ed_type_runtime_data *rt_data =
>> >>> +        engine_get_input_data("runtime_data", node);
>> >>> +
>> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> >>> +
>> >>> +    ovn_port_mirror_run(&pm_ctx);
>> >>> +    engine_set_node_state(node, EN_UPDATED);
>> >>> +}
>> >>> +
>> >>> +static bool
>> >>> +port_mirror_runtime_data_handler(struct engine_node *node, void
>> *data)
>> >>> +{
>> >>> +    struct ed_type_runtime_data *rt_data =
>> >>> +        engine_get_input_data("runtime_data", node);
>> >>> +
>> >>> +    /* There is no tracked data. Fall back to full recompute of
>> >>> port_mirror */
>> >>> +    if (!rt_data->tracked) {
>> >>> +        return false;
>> >>> +    }
>> >>> +
>> >>> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
>> >>> +    if (hmap_is_empty(tracked_dp_bindings)) {
>> >>> +        return true;
>> >>> +    }
>> >>> +
>> >>> +    struct port_mirror_ctx pm_ctx;
>> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> >>> +
>> >>> +    struct tracked_datapath *tdp;
>> >>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>> >>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>> >>> +            /* Fall back to full recompute when a local datapath
>> >>> +             * is added or deleted. */
>> >>> +            return false;
>> >>> +        }
>> >>> +
>> >>> +        struct shash_node *shash_node;
>> >>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>> >>> +            struct tracked_lport *lport = shash_node->data;
>> >>> +            bool removed =
>> >>> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ?
>> >>> true: false;
>> >>> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed,
>> >>> &pm_ctx)) {
>> >>> +                return false;
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    engine_set_node_state(node, EN_UPDATED);
>> >>> +    return true;
>> >>> +}
>> >>> +
>> >>> +static bool
>> >>> +port_mirror_port_binding_handler(struct engine_node *node, void
>> *data)
>> >>> +{
>> >>> +    struct port_mirror_ctx pm_ctx;
>> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> >>> +    struct ed_type_runtime_data *rt_data =
>> >>> +        engine_get_input_data("runtime_data", node);
>> >>> +
>> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> >>> +
>> >>> +    /* handle port binding updates (i.,e when the mirror column
>> >>> +     * of port_binding is updated)
>> >>> +     */
>> >>> +    const struct sbrec_port_binding *pb;
>> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> >>> + pm_ctx.port_binding_table) {
>> >>> +        bool removed = sbrec_port_binding_is_deleted(pb);
>> >>> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
>> >>> +            return false;
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    engine_set_node_state(node, EN_UPDATED);
>> >>> +    return true;
>> >>> +
>> >>> +}
>> >>> +
>> >>> +static bool
>> >>> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
>> >>> +{
>> >>> +    struct port_mirror_ctx pm_ctx;
>> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>> >>> +    struct ed_type_runtime_data *rt_data =
>> >>> +        engine_get_input_data("runtime_data", node);
>> >>> +
>> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> >>> +
>> >>> +    /* handle sb mirror updates
>> >>> +     */
>> >>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>> >>> +        return false;
>> >>> +    }
>> >>> +
>> >>> +    engine_set_node_state(node, EN_UPDATED);
>> >>> +    return true;
>> >>> +
>> >>> +}
>> >>> +
>> >>>   /* Engine node which is used to handle the Non VIF data like
>> >>>    *   - OVS patch ports
>> >>>    *   - Tunnel ports and the related chassis information.
>> >>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>> >>>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>> >>>       ENGINE_NODE(northd_options, "northd_options");
>> >>>       ENGINE_NODE(dhcp_options, "dhcp_options");
>> >>> +    ENGINE_NODE(port_mirror, "port_mirror");
>> >>>
>> >>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>> >>>       SB_NODES
>> >>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>> >>>       engine_add_input(&en_flow_output, &en_pflow_output,
>> >>>                        flow_output_pflow_output_handler);
>> >>>
>> >>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
>> >>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>> >>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>> >>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>> >>> +    engine_add_input(&en_port_mirror, &en_ovs_port,
>> >>> engine_noop_handler);
>> >>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>> >>> +                     engine_noop_handler);
>> >>> +    engine_add_input(&en_flow_output, &en_port_mirror,
>> >>> +                     engine_noop_handler);
>> >>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
>> >>> +                     port_mirror_runtime_data_handler);
>> >>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>> >>> +                     port_mirror_sb_mirror_handler);
>> >>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>> >>> +                     port_mirror_port_binding_handler);
>> >>> +
>> >>>       struct engine_arg engine_arg = {
>> >>>           .sb_idl = ovnsb_idl_loop.idl,
>> >>>           .ovs_idl = ovs_idl_loop.idl,
>> >>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>> >>>
>> >>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>> >>>                                       time_msec());
>> >>> -                    if (ovnsb_idl_txn) {
>> >>> -                        if (ofctrl_has_backlog()) {
>> >>> -                            /* When there are in-flight messages
>> >>> pending to
>> >>> -                             * ovs-vswitchd, we should hold on
>> >>> recomputing so
>> >>> -                             * that the previous flow installations
>> >>> won't be
>> >>> -                             * delayed.  However, we still want to
>> >>> try if
>> >>> -                             * recompute is not needed and we can
>> >>> quickly
>> >>> -                             * incrementally process the new
>> >>> changes, to avoid
>> >>> -                             * unnecessarily forced recomputes
>> >>> later on.  This
>> >>> -                             * is because the OVSDB change tracker
>> >>> cannot
>> >>> -                             * preserve tracked changes across
>> >>> iterations.  If
>> >>> -                             * change tracking is improved, we can
>> >>> simply skip
>> >>> -                             * this round of engine_run and
>> >>> continue processing
>> >>> -                             * acculated changes incrementally
>> >>> later when
>> >>> -                             * ofctrl_has_backlog() returns false. */
>> >>> -                            engine_run(false);
>> >>> -                        } else {
>> >>> -                            engine_run(true);
>> >>> -                        }
>> >>> -                    } else {
>> >>> -                        /* Even if there's no SB DB transaction
>> >>> available,
>> >>> +
>> >>> +                    bool allow_engine_recompute = true;
>> >>> +
>> >>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>> >>> + ofctrl_has_backlog()) {
>> >>> +                        /* When there are in-flight messages
>> >>> pending to
>> >>> +                         * ovs-vswitchd, we should hold on
>> >>> recomputing so
>> >>> +                         * that the previous flow installations
>> >>> won't be
>> >>> +                         * delayed.  However, we still want to try if
>> >>> +                         * recompute is not needed and we can quickly
>> >>> +                         * incrementally process the new changes,
>> >>> to avoid
>> >>> +                         * unnecessarily forced recomputes later
>> >>> on.  This
>> >>> +                         * is because the OVSDB change tracker cannot
>> >>> +                         * preserve tracked changes across
>> >>> iterations.  If
>> >>> +                         * change tracking is improved, we can
>> >>> simply skip
>> >>> +                         * this round of engine_run and continue
>> >>> processing
>> >>> +                         * acculated changes incrementally later when
>> >>> +                         * ofctrl_has_backlog() returns false. */
>> >>> +
>> >>> +                        /* Even if there's no SB/OVS DB transaction
>> >>> available,
>> >>>                            * try to run the engine so that we can
>> >>> handle any
>> >>>                            * incremental changes that don't require
>> >>> a recompute.
>> >>>                            * If a recompute is required, the engine
>> >>> will abort,
>> >>>                            * triggerring a full run in the next
>> >>> iteration.
>> >>>                            */
>> >>> -                        engine_run(false);
>> >>> +                        allow_engine_recompute = false;
>> >>>                       }
>> >>> +
>> >>> +                    engine_run(allow_engine_recompute);
>> >>> +
>> >>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>> >>>                                      time_msec());
>> >>>                       if (engine_has_updated()) {
>> >>> diff --git a/northd/en-northd.c b/northd/en-northd.c
>> >>> index 7fe83db64..608220b1f 100644
>> >>> --- a/northd/en-northd.c
>> >>> +++ b/northd/en-northd.c
>> >>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void
>> >>> *data)
>> >>>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
>> >>>       input_data.nbrec_static_mac_binding_table =
>> >>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>> >>> +    input_data.nbrec_mirror_table =
>> >>> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>> >>>
>> >>>       input_data.sbrec_sb_global_table =
>> >>>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>> >>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node,
>> >>> void *data)
>> >>>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>> >>>       input_data.sbrec_static_mac_binding_table =
>> >>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>> >>> +    input_data.sbrec_mirror_table =
>> >>> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>> >>>
>> >>>       northd_run(&input_data, data,
>> >>>                  eng_ctx->ovnnb_idl_txn,
>> >>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>> >>> index 54e0ad3b0..ac27a730e 100644
>> >>> --- a/northd/inc-proc-northd.c
>> >>> +++ b/northd/inc-proc-northd.c
>> >>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>> >>>       NB_NODE(acl, "acl") \
>> >>>       NB_NODE(logical_router, "logical_router") \
>> >>>       NB_NODE(qos, "qos") \
>> >>> +    NB_NODE(mirror, "mirror") \
>> >>>       NB_NODE(meter, "meter") \
>> >>>       NB_NODE(meter_band, "meter_band") \
>> >>>       NB_NODE(logical_router_port, "logical_router_port") \
>> >>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>> >>>       SB_NODE(logical_flow, "logical_flow") \
>> >>>       SB_NODE(logical_dp_group, "logical_DP_group") \
>> >>>       SB_NODE(multicast_group, "multicast_group") \
>> >>> +    SB_NODE(mirror, "mirror") \
>> >>>       SB_NODE(meter, "meter") \
>> >>>       SB_NODE(meter_band, "meter_band") \
>> >>>       SB_NODE(datapath_binding, "datapath_binding") \
>> >>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
>> >>> *nb,
>> >>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
>> >>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>> >>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
>> >>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>> >>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
>> >>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>> >>>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
>> >>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
>> >>> *nb,
>> >>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
>> >>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
>> >>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>> >>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>> >>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
>> >>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>> >>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>> >>> diff --git a/northd/northd.c b/northd/northd.c
>> >>> index b7388afc5..52abdda28 100644
>> >>> --- a/northd/northd.c
>> >>> +++ b/northd/northd.c
>> >>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>> >>>       free(requested_chassis_sb);
>> >>>   }
>> >>>
>> >>> +static void
>> >>> +do_sb_mirror_addition(struct northd_input *input_data,
>> >>> +                      const struct ovn_port *op)
>> >>> +{
>> >>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> >>> +        const struct sbrec_mirror *sb_mirror;
>> >>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>> >>> + input_data->sbrec_mirror_table) {
>> >>> +            if (!strcmp(sb_mirror->name,
>> >>> + op->nbsp->mirror_rules[i]->name)) {
>> >>> +                /* Add the value to SB */
>> >>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>> >>> + sb_mirror);
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +sbrec_port_binding_update_mirror_rules(struct northd_input
>> >>> *input_data,
>> >>> +                                       const struct ovn_port *op)
>> >>> +{
>> >>> +    size_t i = 0;
>> >>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>> >>> +        /* Needs deletion in SB */
>> >>> +        struct shash nb_mirror_rules =
>> >>> SHASH_INITIALIZER(&nb_mirror_rules);
>> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> >>> +            shash_add(&nb_mirror_rules,
>> >>> + op->nbsp->mirror_rules[i]->name,
>> >>> + op->nbsp->mirror_rules[i]);
>> >>> +        }
>> >>> +
>> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>> >>> +            if (!shash_find(&nb_mirror_rules,
>> >>> + op->sb->mirror_rules[i]->name)) {
>> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>> >>> + op->sb->mirror_rules[i]);
>> >>> +            }
>> >>> +        }
>> >>> +
>> >>> +        struct shash_node *node, *next;
>> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>> >>> +            shash_delete(&nb_mirror_rules, node);
>> >>> +        }
>> >>> +        shash_destroy(&nb_mirror_rules);
>> >>> +
>> >>> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
>> >>> +        /* Needs addition in SB */
>> >>> +        do_sb_mirror_addition(input_data, op);
>> >>> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
>> >>> +        /*
>> >>> +         * Check if its the same mirrors on both SB and NB DBs
>> >>> +         * If not update accordingly.
>> >>> +         */
>> >>> +        bool needs_sb_addition = false;
>> >>> +        struct shash nb_mirror_rules =
>> >>> SHASH_INITIALIZER(&nb_mirror_rules);
>> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> >>> +            shash_add(&nb_mirror_rules,
>> >>> + op->nbsp->mirror_rules[i]->name,
>> >>> + op->nbsp->mirror_rules[i]);
>> >>> +        }
>> >>> +
>> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>> >>> +            if (!shash_find(&nb_mirror_rules,
>> >>> + op->sb->mirror_rules[i]->name)) {
>> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>> >>> + op->sb->mirror_rules[i]);
>> >>> +                    needs_sb_addition = true;
>> >>> +            }
>> >>> +        }
>> >>> +
>> >>> +        struct shash_node *node, *next;
>> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>> >>> +            shash_delete(&nb_mirror_rules, node);
>> >>> +        }
>> >>> +        shash_destroy(&nb_mirror_rules);
>> >>> +
>> >>> +        if (needs_sb_addition) {
>> >>> +            do_sb_mirror_addition(input_data, op);
>> >>> +        }
>> >>> +    }
>> >>> +}
>> >>> +
>> >>>   static void
>> >>>   ovn_port_update_sbrec(struct northd_input *input_data,
>> >>>                         struct ovsdb_idl_txn *ovnsb_txn,
>> >>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input
>> >>> *input_data,
>> >>>           }
>> >>>           sbrec_port_binding_set_external_ids(op->sb, &ids);
>> >>>           smap_destroy(&ids);
>> >>> +
>> >>> +        if (!op->nbsp->n_mirror_rules) {
>> >>> +            /* Nothing is set. Clear mirror_rules from pb. */
>> >>> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>> >>> +        } else {
>> >>> +            /* Check if SB DB update needed */
>> >>> + sbrec_port_binding_update_mirror_rules(input_data, op);
>> >>> +        }
>> >>> +
>> >>>       }
>> >>>       if (op->tunnel_key != op->sb->tunnel_key) {
>> >>>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>> >>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>> >>>       shash_destroy(&sb_meters);
>> >>>   }
>> >>>
>> >>> +static bool
>> >>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>> >>> +                  const struct sbrec_mirror *sb_mirror)
>> >>> +{
>> >>> +
>> >>> +    if (nb_mirror->index != sb_mirror->index) {
>> >>> +        return true;
>> >>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>> >>> +        return true;
>> >>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>> >>> +        return true;
>> >>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>> >>> +        return true;
>> >>> +    }
>> >>> +
>> >>> +    return false;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>> >>> +                             const char *mirror_name,
>> >>> +                             const struct nbrec_mirror *nb_mirror,
>> >>> +                             struct shash *sb_mirrors,
>> >>> +                             struct sset *used_sb_mirrors)
>> >>> +{
>> >>> +    const struct sbrec_mirror *sb_mirror;
>> >>> +    bool new_sb_mirror = false;
>> >>> +
>> >>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>> >>> +    if (!sb_mirror) {
>> >>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>> >>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>> >>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>> >>> +        new_sb_mirror = true;
>> >>> +    }
>> >>> +    sset_add(used_sb_mirrors, mirror_name);
>> >>> +
>> >>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror,
>> >>> sb_mirror)) {
>> >>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>> >>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>> >>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>> >>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +sync_mirrors(struct northd_input *input_data,
>> >>> +            struct ovsdb_idl_txn *ovnsb_txn)
>> >>> +{
>> >>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>> >>> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
>> >>> +
>> >>> +    const struct sbrec_mirror *sb_mirror;
>> >>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>> >>> input_data->sbrec_mirror_table) {
>> >>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>> >>> +    }
>> >>> +
>> >>> +    const struct nbrec_mirror *nb_mirror;
>> >>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
>> >>> input_data->nbrec_mirror_table) {
>> >>> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
>> >>> nb_mirror,
>> >>> +                                     &sb_mirrors, &used_sb_mirrors);
>> >>> +    }
>> >>> +
>> >>> +    const char *used_mirror;
>> >>> +    const char *used_mirror_next;
>> >>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
>> >>> &used_sb_mirrors) {
>> >>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
>> >>> +        sset_delete(&used_sb_mirrors,
>> >>> SSET_NODE_FROM_NAME(used_mirror));
>> >>> +    }
>> >>> +    sset_destroy(&used_sb_mirrors);
>> >>> +
>> >>> +    struct shash_node *node, *next;
>> >>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>> >>> +        sbrec_mirror_delete(node->data);
>> >>> +        shash_delete(&sb_mirrors, node);
>> >>> +    }
>> >>> +    shash_destroy(&sb_mirrors);
>> >>> +}
>> >>> +
>> >>>   /*
>> >>>    * struct 'dns_info' is used to sync the DNS records between OVN
>> >>> Northbound db
>> >>>    * and Southbound db.
>> >>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>> >>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>> >>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>> >>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>> >>> +    sync_mirrors(input_data, ovnsb_txn);
>> >>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>> >>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
>> >>>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>> >>> diff --git a/northd/northd.h b/northd/northd.h
>> >>> index da90e2815..17a62ea10 100644
>> >>> --- a/northd/northd.h
>> >>> +++ b/northd/northd.h
>> >>> @@ -36,6 +36,7 @@ struct northd_input {
>> >>>       const struct nbrec_acl_table *nbrec_acl_table;
>> >>>       const struct nbrec_static_mac_binding_table
>> >>>           *nbrec_static_mac_binding_table;
>> >>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>> >>>
>> >>>       /* Southbound table references */
>> >>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
>> >>> @@ -55,6 +56,7 @@ struct northd_input {
>> >>>       const struct sbrec_chassis_private_table
>> >>> *sbrec_chassis_private_table;
>> >>>       const struct sbrec_static_mac_binding_table
>> >>>           *sbrec_static_mac_binding_table;
>> >>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>> >>>
>> >>>       /* Indexes */
>> >>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
>> >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>> >>> index 174364c8b..01de55222 100644
>> >>> --- a/ovn-nb.ovsschema
>> >>> +++ b/ovn-nb.ovsschema
>> >>> @@ -1,7 +1,7 @@
>> >>>   {
>> >>>       "name": "OVN_Northbound",
>> >>> -    "version": "6.3.0",
>> >>> -    "cksum": "4042813038 31869",
>> >>> +    "version": "6.4.0",
>> >>> +    "cksum": "589874483 33352",
>> >>>       "tables": {
>> >>>           "NB_Global": {
>> >>>               "columns": {
>> >>> @@ -132,6 +132,11 @@
>> >>>                                               "refType": "weak"},
>> >>>                                    "min": 0,
>> >>>                                    "max": 1}},
>> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> >>> +                                          "refTable": "Mirror",
>> >>> +                                          "refType": "weak"},
>> >>> +                                  "min": 0,
>> >>> +                                  "max": "unlimited"}},
>> >>>                   "ha_chassis_group": {
>> >>>                       "type": {"key": {"type": "uuid",
>> >>>                                        "refTable": "HA_Chassis_Group",
>> >>> @@ -301,6 +306,28 @@
>> >>>                       "type": {"key": "string", "value": "string",
>> >>>                                "min": 0, "max": "unlimited"}}},
>> >>>               "isRoot": false},
>> >>> +        "Mirror": {
>> >>> +            "columns": {
>> >>> +                "name": {"type": "string"},
>> >>> +                "filter": {"type": {"key": {"type": "string",
>> >>> +                                            "enum": ["set",
>> >>> ["from-lport",
>> >>> + "to-lport",
>> >>> + "both"]]}}},
>> >>> +                "sink":{"type": "string"},
>> >>> +                "type": {"type": {"key": {"type": "string",
>> >>> +                                            "enum": ["set", ["gre",
>> >>> + "erspan"]]}}},
>> >>> +                "index": {"type": "integer"},
>> >>> +                "src": {"type": {"key": {"type": "uuid",
>> >>> +                                           "refTable":
>> >>> "Logical_Switch_Port",
>> >>> +                                           "refType": "weak"},
>> >>> +                                   "min": 0,
>> >>> +                                   "max": "unlimited"}},
>> >>> +                "external_ids": {
>> >>> +                    "type": {"key": "string", "value": "string",
>> >>> +                             "min": 0, "max": "unlimited"}}},
>> >>> +            "indexes": [["name"]],
>> >>> +            "isRoot": true},
>> >>>           "Meter": {
>> >>>               "columns": {
>> >>>                   "name": {"type": "string"},
>> >>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>> >>> index f41e9d7c0..d8730c8fc 100644
>> >>> --- a/ovn-nb.xml
>> >>> +++ b/ovn-nb.xml
>> >>> @@ -1554,6 +1554,11 @@
>> >>>         </column>
>> >>>       </group>
>> >>>
>> >>> +    <column name="mirror_rules">
>> >>> +        Mirror rules that apply to logical switch port which is the
>> >>> source.
>> >>> +        Please see the <ref table="Mirror"/> table.
>> >>> +    </column>
>> >>> +
>> >>>       <column name="ha_chassis_group">
>> >>>         References a row in the OVN Northbound database's
>> >>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>> >>> @@ -2491,6 +2496,64 @@
>> >>>       </column>
>> >>>     </table>
>> >>>
>> >>> +  <table name="Mirror" title="Mirror Entry">
>> >>> +    <p>
>> >>> +      Each row in this table represents one Mirror that can be used
>> >>> for
>> >>> +      port mirroring. These Mirrors are referenced by the
>> >>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/>
>> >>> column in
>> >>> +      the <ref table="Logical_Switch_Port"/> table.
>> >>> +    </p>
>> >>> +
>> >>> +    <column name="name">
>> >>> +      <p>
>> >>> +        Represents the name of the mirror.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="filter">
>> >>> +      <p>
>> >>> +        The value of this field represents selection criteria of
>> >>> the mirror.
>> >>> +        Supported values for filter to-lport / from-lport / both
>> >>> +        to-lport - to mirror packets coming into logical port
>> >>> +        from-lport - to mirror packets going out of logical port
>> >>> +        both - to mirror packets coming into and going out of
>> >>> logical port.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="sink">
>> >>> +      <p>
>> >>> +        The value of this field represents the destination/sink of
>> >>> the mirror.
>> >>> +        The value it takes is an IP address of the sink port.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="type">
>> >>> +      <p>
>> >>> +        The value of this field represents the type of the tunnel
>> >>> used for
>> >>> +        sending the mirrored packets. Supported Tunnel types gre
>> >>> and erspan
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="index">
>> >>> +      <p>
>> >>> +        The value of this field represents the tunnel ID. Depending
>> >>> on the
>> >>> +        tunnel type configured, GRE key value if type GRE and
>> >>> erspan_idx value
>> >>> +        if ERSPAN
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="src">
>> >>> +      <p>
>> >>> +        The value of this field represents a list of source ports
>> >>> for the
>> >>> +        mirror. Please see the <ref table="Logical_Switch_Port"/>
>> >>> table.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="external_ids">
>> >>> +      See <em>External IDs</em> at the beginning of this document.
>> >>> +    </column>
>> >>> +  </table>
>> >>> +
>> >>>     <table name="Meter" title="Meter entry">
>> >>>       <p>
>> >>>         Each row in this table represents a meter that can be used
>> >>> for QoS or
>> >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>> >>> index 576ebbdeb..b83134416 100644
>> >>> --- a/ovn-sb.ovsschema
>> >>> +++ b/ovn-sb.ovsschema
>> >>> @@ -1,7 +1,7 @@
>> >>>   {
>> >>>       "name": "OVN_Southbound",
>> >>> -    "version": "20.25.0",
>> >>> -    "cksum": "53184112 28845",
>> >>> +    "version": "20.26.0",
>> >>> +    "cksum": "2344151793 30004",
>> >>>       "tables": {
>> >>>           "SB_Global": {
>> >>>               "columns": {
>> >>> @@ -142,6 +142,23 @@
>> >>>               "indexes": [["datapath", "tunnel_key"],
>> >>>                           ["datapath", "name"]],
>> >>>               "isRoot": true},
>> >>> +        "Mirror": {
>> >>> +            "columns": {
>> >>> +                "name": {"type": "string"},
>> >>> +                "filter": {"type": {"key": {"type": "string",
>> >>> +                                            "enum": ["set",
>> >>> + ["from-lport",
>> >>> + "to-lport","both"]]}}},
>> >>> +                "sink":{"type": "string"},
>> >>> +                "type": {"type": {"key": {"type": "string",
>> >>> +                                            "enum": ["set",
>> >>> +                                                     ["gre",
>> >>> "erspan"]]}}},
>> >>> +                "index": {"type": "integer"},
>> >>> +                "external_ids": {
>> >>> +                    "type": {"key": "string", "value": "string",
>> >>> +                             "min": 0, "max": "unlimited"}}},
>> >>> +            "indexes": [["name"]],
>> >>> +            "isRoot": true},
>> >>>           "Meter": {
>> >>>               "columns": {
>> >>>                   "name": {"type": "string"},
>> >>> @@ -230,6 +247,11 @@
>> >>> "refTable": "Encap",
>> >>> "refType": "weak"},
>> >>>                                       "min": 0, "max": "unlimited"}},
>> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> >>> +                                          "refTable": "Mirror",
>> >>> +                                          "refType": "weak"},
>> >>> +                                  "min": 0,
>> >>> +                                  "max": "unlimited"}},
>> >>>                   "mac": {"type": {"key": "string",
>> >>>                                    "min": 0,
>> >>>                                    "max": "unlimited"}},
>> >>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>> >>> index 315d60853..05c0db6b4 100644
>> >>> --- a/ovn-sb.xml
>> >>> +++ b/ovn-sb.xml
>> >>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>> >>>       </column>
>> >>>     </table>
>> >>>
>> >>> +  <table name="Mirror" title="Mirror Entry">
>> >>> +    <p>
>> >>> +      Each row in this table represents one Mirror that can be used
>> >>> for
>> >>> +      port mirroring. These Mirrors are referenced by the
>> >>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
>> >>> +      the <ref table="Port_Binding"/> table.
>> >>> +    </p>
>> >>> +
>> >>> +    <column name="name">
>> >>> +      <p>
>> >>> +        Represents the name of the mirror.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="filter">
>> >>> +      <p>
>> >>> +        The value of this field represents selection criteria of
>> >>> the mirror.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="sink">
>> >>> +      <p>
>> >>> +        The value of this field represents the destination/sink of
>> >>> the mirror.
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="type">
>> >>> +      <p>
>> >>> +        The value of this field represents the type of the tunnel
>> >>> used for
>> >>> +        sending the mirrored packets
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="index">
>> >>> +      <p>
>> >>> +        The value of this field represents the key/idx depending on
>> >>> the
>> >>> +        tunnel type configured
>> >>> +      </p>
>> >>> +    </column>
>> >>> +
>> >>> +    <column name="external_ids">
>> >>> +      See <em>External IDs</em> at the beginning of this document.
>> >>> +    </column>
>> >>> +  </table>
>> >>> +
>> >>>     <table name="Meter" title="Meter entry">
>> >>>       <p>
>> >>>         Each row in this table represents a meter that can be used
>> >>> for QoS or
>> >>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>> >>>         </column>
>> >>>       </group>
>> >>>
>> >>> +    <column name="mirror_rules">
>> >>> +        Mirror rules that apply to the port binding.
>> >>> +        Please see the <ref table="Mirror"/> table.
>> >>> +    </column>
>> >>> +
>> >>>       <group title="Patch Options">
>> >>>         <p>
>> >>>           These options apply to logical ports with <ref
>> >>> column="type"/> of
>> >>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
>> >>> index 4d480e357..d79f9d929 100644
>> >>> --- a/tests/ovn-nbctl.at
>> >>> +++ b/tests/ovn-nbctl.at
>> >>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>> >>>
>> >>>   dnl
>> >>> ---------------------------------------------------------------------
>> >>>
>> >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
>> >>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>> >>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
>> >>> +AT_CHECK([ovn-nbctl ls-add sw0])
>> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>> >>> +
>> >>> +dnl Add duplicate mirror name
>> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
>> >>> 10.10.10.5], [1], [], [stderr])
>> >>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>> >>> +
>> >>> +dnl Attach invalid source port to mirror
>> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [],
>> >>> [stderr])
>> >>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>> >>> +
>> >>> +dnl Attach source port to invalid mirror
>> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [],
>> >>> [stderr])
>> >>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
>> >>> +
>> >>> +dnl Attach source port to mirror
>> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>> >>> +
>> >>> +dnl Attach one more source port to mirror
>> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>> >>> +
>> >>> +dnl Verify if multiple ports are attached to the same mirror properly
>> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> >>> +mirror1:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  10.10.10.1
>> >>> +  Filter   :  from-lport
>> >>> +  Index/Key:  0
>> >>> +  Sources  :  None attached
>> >>> +mirror2:
>> >>> +  Type     :  erspan
>> >>> +  Sink     :  10.10.10.2
>> >>> +  Filter   :  both
>> >>> +  Index/Key:  1
>> >>> +  Sources  :  None attached
>> >>> +mirror3:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  10.10.10.3
>> >>> +  Filter   :  to-lport
>> >>> +  Index/Key:  2
>> >>> +  Sources  :  sw0-port1  sw0-port3
>> >>> +])
>> >>> +
>> >>> +dnl Detach one source port from mirror
>> >>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>> >>> +
>> >>> +dnl Verify if detach source port from mirror happens properly
>> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> >>> +mirror1:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  10.10.10.1
>> >>> +  Filter   :  from-lport
>> >>> +  Index/Key:  0
>> >>> +  Sources  :  None attached
>> >>> +mirror2:
>> >>> +  Type     :  erspan
>> >>> +  Sink     :  10.10.10.2
>> >>> +  Filter   :  both
>> >>> +  Index/Key:  1
>> >>> +  Sources  :  None attached
>> >>> +mirror3:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  10.10.10.3
>> >>> +  Filter   :  to-lport
>> >>> +  Index/Key:  2
>> >>> +  Sources  :  sw0-port1
>> >>> +])
>> >>> +
>> >>> +dnl Delete a single mirror which has source attached.
>> >>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
>> >>> +
>> >>> +dnl Check if the detach happened from source properly
>> >>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules
>> >>> |  cut -b 3], [0], [dnl
>> >>> +
>> >>> +])
>> >>> +
>> >>> +dnl Check if the mirror deleted properly
>> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> >>> +mirror1:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  10.10.10.1
>> >>> +  Filter   :  from-lport
>> >>> +  Index/Key:  0
>> >>> +  Sources  :  None attached
>> >>> +mirror2:
>> >>> +  Type     :  erspan
>> >>> +  Sink     :  10.10.10.2
>> >>> +  Filter   :  both
>> >>> +  Index/Key:  1
>> >>> +  Sources  :  None attached
>> >>> +])
>> >>> +
>> >>> +dnl Delete another mirror
>> >>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
>> >>> +
>> >>> +dnl Update the Sink address
>> >>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>> >>> +
>> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> >>> +mirror1:
>> >>> +  Type     :  gre
>> >>> +  Sink     :  192.168.1.13
>> >>> +  Filter   :  from-lport
>> >>> +  Index/Key:  0
>> >>> +  Sources  :  None attached
>> >>> +])
>> >>> +
>> >>> +dnl Delete all mirrors
>> >>> +AT_CHECK([ovn-nbctl mirror-del])
>> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> >>> +])])
>> >>> +
>> >>> +dnl
>> >>> ---------------------------------------------------------------------
>> >>> +
>> >>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>> >>>   AT_CHECK([ovn-nbctl lr-add lr0])
>> >>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2],
>> >>> [1], [],
>> >>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> >>> index 4f399eccb..4e6c268e4 100644
>> >>> --- a/tests/ovn-northd.at
>> >>> +++ b/tests/ovn-northd.at
>> >>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1}
>> >>> meter_me__${acl2}
>> >>>   AT_CLEANUP
>> >>>   ])
>> >>>
>> >>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> >>> +AT_SETUP([Check NB-SB mirrors sync])
>> >>> +AT_KEYWORDS([mirrors])
>> >>> +ovn_start
>> >>> +
>> >>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> >>> +"10.10.10.2"
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> >>> +erspan
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> >>> +0
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> >>> +both
>> >>> +])
>> >>> +
>> >>> +check ovn-nbctl --wait=sb \
>> >>> +    -- set mirror . sink=192.168.1.13
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> >>> +"192.168.1.13"
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> >>> +erspan
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> >>> +0
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> >>> +both
>> >>> +])
>> >>> +
>> >>> +check ovn-nbctl --wait=sb \
>> >>> +    -- set mirror . type=gre
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> >>> +gre
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> >>> +"192.168.1.13"
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> >>> +0
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> >>> +both
>> >>> +])
>> >>> +
>> >>> +check ovn-nbctl --wait=sb \
>> >>> +    -- set mirror . index=12
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> >>> +12
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> >>> +gre
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> >>> +"192.168.1.13"
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> >>> +both
>> >>> +])
>> >>> +
>> >>> +check ovn-nbctl --wait=sb \
>> >>> +    -- set mirror . filter=to-lport
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> >>> +to-lport
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> >>> +12
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> >>> +gre
>> >>> +])
>> >>> +
>> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> >>> +"192.168.1.13"
>> >>> +])
>> >>> +
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>>   OVN_FOR_EACH_NORTHD_NO_HV([
>> >>>   AT_SETUP([ACL skip hints for stateless config])
>> >>>   AT_KEYWORDS([acl])
>> >>> diff --git a/tests/ovn.at b/tests/ovn.at
>> >>> index f8b8db4df..cd5527ea1 100644
>> >>> --- a/tests/ovn.at
>> >>> +++ b/tests/ovn.at
>> >>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>> >>>   AT_CLEANUP
>> >>>   ])
>> >>>
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror])
>> >>> +AT_KEYWORDS([Mirror])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>> >>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>> >>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
>> >>> +    ofport-request=1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>> >>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>> >>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
>> >>> +    ofport-request=1
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +ovn-nbctl dump-flows > sbflows
>> >>> +AT_CAPTURE_FILE([sbflows])
>> >>> +
>> >>> +for i in 1 2; do
>> >>> +    : > vif$i.expected
>> >>> +done
>> >>> +
>> >>> +net_add n2
>> >>> +
>> >>> +sim_add hv2
>> >>> +as hv2
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:02:02:00\"
>> >>> +ovn_attach n2 br-phys 192.168.1.12
>> >>> +
>> >>> +OVN_POPULATE_ARP
>> >>> +
>> >>> +as hv1
>> >>> +
>> >>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST
>> >>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE
>> FILTER
>> >>> +#
>> >>> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
>> >>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
>> >>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
>> >>> +# provided, then it should be the ip and icmp checksums of the packet
>> >>> +# responded; otherwise, no reply is expected.
>> >>> +# In the absence of an ip checksum calculation helpers, this relies
>> >>> +# on the caller to provide the checksums for the ip and icmp headers.
>> >>> +# XXX This should be more systematic.
>> >>> +#
>> >>> +# INPORT is an lport number, e.g. 11 for vif11.
>> >>> +# ETH_SRC and ETH_DST are each 12 hex digits.
>> >>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>> >>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>> >>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>> >>> +# ENCAP_TYPE - gre or erspan
>> >>> +# FILTER - Mirror Filter - to-lport / from-lport
>> >>> +test_ipv4_icmp_request() {
>> >>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5
>> >>> ip_chksum=$6 icmp_chksum=$7
>> >>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9
>> >>> mirror_encap_type=${10} mirror_filter=${11}
>> >>> +    shift; shift; shift; shift; shift; shift; shift
>> >>> +    shift; shift; shift; shift;
>> >>> +
>> >>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>> >>> +    local ip_ttl=02
>> >>> +    local icmp_id=5fbf
>> >>> +    local icmp_seq=0001
>> >>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>> >>> +    local icmp_type_code_request=0800
>> >>> +    local
>> >>>
>> icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>> >>> +    local
>> >>>
>> packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>> >>> +
>> >>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
>> >>> +
>> >>> +    # Expect to receive the reply, if any. In same port where
>> >>> packet was sent.
>> >>> +    # Note: src and dst fields are expected to be reversed.
>> >>> +    local icmp_type_code_response=0000
>> >>> +    local reply_icmp_ttl=fe
>> >>> +    local
>> >>>
>> reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>> >>> +    local
>> >>>
>> reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>> >>> +    echo $reply >> vif$inport.expected
>> >>> +    local remote_mac=000000020200
>> >>> +    local local_mac=000000010200
>> >>> +    if test ${mirror_encap_type} = "gre" ; then
>> >>> +        local
>> >>> ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>> >>> +        if test ${mirror_filter} = "to-lport" ; then
>> >>> +            local
>> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>> >>> +        elif test ${mirror_filter} = "from-lport" ; then
>> >>> +            local
>> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>> >>> +        fi
>> >>> +    elif test ${mirror_encap_type} = "erspan" ; then
>> >>> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>> >>> +        local erspan_seq0=100088be000000001000000000000000
>> >>> +        local erspan_seq1=100088be000000011000000000000000
>> >>> +        if test ${mirror_filter} = "to-lport" ; then
>> >>> +            local
>> >>>
>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
>> >>>
>> >>> +        elif test ${mirror_filter} = "from-lport" ; then
>> >>> +            local
>> >>>
>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>> >>> +        fi
>> >>> +    fi
>> >>> +    echo $mirror >> br-phys_n1.expected
>> >>> +
>> >>> +}
>> >>> +
>> >>> +# Set IPs
>> >>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>> >>> +l1_ip=$(ip_to_hex 192 168 1 2)
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +
>> >>> +# Send ping packet and check for mirrored packet of the reply
>> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>> >>> +
>> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>> >>> [br-phys_n1.expected])
>> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> >>> +
>> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> >>> +rm -f br-phys_n1.expected
>> >>> +rm -f vif1.expected
>> >>> +
>> >>> +check ovn-nbctl set mirror . type=erspan
>> >>> +
>> >>> +# Send ping packet and check for mirrored packet of the reply
>> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>> >>> +
>> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>> >>> [br-phys_n1.expected])
>> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> >>> +
>> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> >>> +rm -f br-phys_n1.expected
>> >>> +rm -f vif1.expected
>> >>> +
>> >>> +check ovn-nbctl set mirror . filter=from-lport
>> >>> +
>> >>> +# Send ping packet and check for mirrored packet of the request
>> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>> >>> +
>> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>> >>> [br-phys_n1.expected])
>> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> >>> +
>> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>> >>> +rm -f br-phys_n1.expected
>> >>> +rm -f vif1.expected
>> >>> +
>> >>> +check ovn-nbctl set mirror . type=gre
>> >>> +
>> >>> +# Send ping packet and check for mirrored packet of the request
>> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>> >>> +
>> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>> >>> [br-phys_n1.expected])
>> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>> >>> +
>> >>> +echo "---------OVN NB Mirror-----"
>> >>> +ovn-nbctl mirror-list
>> >>> +
>> >>> +echo "---------OVS Mirror----"
>> >>> +ovs-vsctl list Mirror
>> >>> +
>> >>> +echo "-----------------------"
>> >>> +
>> >>> +echo "Verifying Mirror deletion in OVS"
>> >>> +# Set vif1 iface-id such that OVN releases port binding
>> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>> >>> +])
>> >>> +
>> >>> +# Set vif1 iface-id back to ls1-lp1
>> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) =
>> >>> "mirror0"])
>> >>> +
>> >>> +# Delete vif1 so that OVN releases port binding
>> >>> +check ovs-vsctl del-port br-int vif1
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>> >>> +
>> >>> +OVN_CLEANUP([hv1], [hv2])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror test bulk swap attachments])
>> >>> +AT_KEYWORDS([Mirror test bulk swap attachments])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls1-lp2 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp2 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif3 -- \
>> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif4 -- \
>> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +# Equal detaches and attaches
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> >>> +
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>> >>> +
>> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>> >>> +
>> >>> +OVN_CLEANUP([hv1])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror test bulk attach multiple])
>> >>> +AT_KEYWORDS([Mirror test bulk attach multiple])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls1-lp2 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp2 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif3 -- \
>> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif4 -- \
>> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +check ovn-nbctl mirror-del
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +
>> >>> +# Attaches multiple mirrors
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>> >>> +
>> >>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>> >>> +
>> >>> +OVN_CLEANUP([hv1])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror test bulk more detach and less attach])
>> >>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls1-lp2 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp2 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif3 -- \
>> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif4 -- \
>> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +check ovn-nbctl mirror-del
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +# Detaches more than attaches
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
>> >>> +
>> >>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
>> >>> +
>> >>> +OVN_CLEANUP([hv1])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror test bulk attach more than detach])
>> >>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls1-lp2 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp2 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif3 -- \
>> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif4 -- \
>> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +# Attaches more than detaches
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +as hv1
>> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>> >>> +
>> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>> >>> +
>> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>> >>> +
>> >>> +OVN_CLEANUP([hv1])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>> +
>> >>> +OVN_FOR_EACH_NORTHD([
>> >>> +AT_SETUP([Mirror test bulk detach multiple])
>> >>> +AT_KEYWORDS([Mirror test bulk detach multiple])
>> >>> +ovn_start
>> >>> +
>> >>> +# Logical network:
>> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
>> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
>> >>> +
>> >>> +ovn-nbctl lr-add R1
>> >>> +
>> >>> +ovn-nbctl ls-add ls1
>> >>> +ovn-nbctl ls-add ls2
>> >>> +
>> >>> +# Connect ls1 to R1
>> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
>> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>> >>> +    type=router options:router-port=ls1
>> >>> addresses=\"00:00:00:01:02:f1\"
>> >>> +
>> >>> +# Connect ls2 to R1
>> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>> >>> +    type=router options:router-port=ls2
>> >>> addresses=\"00:00:00:01:02:f2\"
>> >>> +
>> >>> +# Create logical port ls1-lp1 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>> >>> +
>> >>> +# Create logical port ls1-lp2 in ls1
>> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>> >>> +
>> >>> +# Create logical port ls2-lp1 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>> >>> +
>> >>> +# Create logical port ls2-lp2 in ls2
>> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>> >>> +
>> >>> +ovn-nbctl lsp-add ls1 ln-public
>> >>> +ovn-nbctl lsp-set-type ln-public localnet
>> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>> >>> +
>> >>> +# Create one hypervisor and create OVS ports corresponding to
>> >>> logical ports.
>> >>> +net_add n1
>> >>> +
>> >>> +sim_add hv1
>> >>> +as hv1
>> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>> >>> +ovn_attach n1 br-phys 192.168.1.11
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif1 -- \
>> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif2 -- \
>> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif3 -- \
>> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>> >>> +
>> >>> +ovs-vsctl -- add-port br-int vif4 -- \
>> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>> >>> +
>> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>> >>> +
>> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
>> >>> +wait_for_ports_up
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>> >>> +
>> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>> >>> +
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +# Detaches all
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>> >>> +check ovn-nbctl --wait=hv sync
>> >>> +
>> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>> >>> +
>> >>> +OVN_CLEANUP([hv1])
>> >>> +AT_CLEANUP
>> >>> +])
>> >>>
>> >>>   OVN_FOR_EACH_NORTHD([
>> >>>   AT_SETUP([Port Groups])
>> >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>> >>> index 811468dc6..af2e61435 100644
>> >>> --- a/utilities/ovn-nbctl.c
>> >>> +++ b/utilities/ovn-nbctl.c
>> >>> @@ -271,6 +271,19 @@ QoS commands:\n\
>> >>>                               remove QoS rules from SWITCH\n\
>> >>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>> >>>   \n\
>> >>> +Mirror commands:\n\
>> >>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
>> >>> +                            add a mirror with given name\n\
>> >>> +                            specify TYPE 'gre' or 'erspan'\n\
>> >>> +                            specify the tunnel INDEX value\n\
>> >>> +                                (indicates key if GRE\n\
>> >>> +                                 erpsan_idx if ERSPAN)\n\
>> >>> +                            specify FILTER for mirroring selection\n\
>> >>> +                                'to-lport' / 'from-lport' / 'both'\n\
>> >>> +                            specify Sink / Destination i.e. Remote
>> >>> IP\n\
>> >>> +  mirror-del [NAME]         remove mirrors\n\
>> >>> +  mirror-list               print mirrors\n\
>> >>> +\n\
>> >>>   Meter commands:\n\
>> >>>     [--fair]\n\
>> >>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
>> >>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>> >>>                               set dhcpv6 options for PORT\n\
>> >>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>> >>>     lsp-get-ls PORT           get the logical switch which the port
>> >>> belongs to\n\
>> >>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
>> >>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>> >>>   \n\
>> >>>   Forwarding group commands:\n\
>> >>>     [--liveness]\n\
>> >>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>> >>>       ovsdb_idl_add_column(ctx->idl,
>> >>> &nbrec_logical_switch_port_col_type);
>> >>>   }
>> >>>
>> >>> +static void
>> >>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>> >>> +{
>> >>> +    ovsdb_idl_add_column(ctx->idl,
>> >>> &nbrec_logical_switch_port_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl,
>> >>> + &nbrec_logical_switch_port_col_mirror_rules);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> >>> +}
>> >>> +
>> >>> +static int
>> >>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
>> >>> +{
>> >>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>> >>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>> >>> +
>> >>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
>> >>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
>> >>> +
>> >>> +    return strcmp(mirror1->name,mirror2->name);
>> >>> +}
>> >>> +
>> >>> +static char * OVS_WARN_UNUSED_RESULT
>> >>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>> >>> +                    bool must_exist,
>> >>> +                    const struct nbrec_mirror **mirror_p)
>> >>> +{
>> >>> +    const struct nbrec_mirror *mirror = NULL;
>> >>> +    *mirror_p = NULL;
>> >>> +
>> >>> +    struct uuid mirror_uuid;
>> >>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>> >>> +    if (is_uuid) {
>> >>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
>> >>> +    }
>> >>> +
>> >>> +    if (!mirror) {
>> >>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> >>> +            if (!strcmp(mirror->name, id)) {
>> >>> +                break;
>> >>> +            }
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    if (!mirror && must_exist) {
>> >>> +        return xasprintf("%s: mirror %s not found",
>> >>> +                         id, is_uuid ? "UUID" : "name");
>> >>> +    }
>> >>> +
>> >>> +    *mirror_p = mirror;
>> >>> +    return NULL;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>> >>> +{
>> >>> +    const char *port = ctx->argv[1];
>> >>> +    const char *mirror_name = ctx->argv[2];
>> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>> >>> +    const struct nbrec_mirror *mirror;
>> >>> +
>> >>> +    char *error;
>> >>> +
>> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +
>> >>> +    /*check if a mirror rule actually exists on that name or not*/
>> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +    /* Check if same mirror rule already exists for the lsp */
>> >>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>> >>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>> >>> +            bool may_exist = shash_find(&ctx->options,
>> >>> "--may-exist") != NULL;
>> >>> +            if (!may_exist) {
>> >>> +                ctl_error(ctx, "Same mirror already existed on the
>> >>> lsp %s.",
>> >>> +                          ctx->argv[1]);
>> >>> +                return;
>> >>> +            }
>> >>> +            return;
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
>> >>> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
>> >>> +
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>> >>> +{
>> >>> +    const char *port = ctx->argv[1];
>> >>> +    const char *mirror_name = ctx->argv[2];
>> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>> >>> +    const struct nbrec_mirror *mirror;
>> >>> +
>> >>> +    char *error;
>> >>> +
>> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +
>> >>> +    /*check if a mirror rule actually exists on that name or not*/
>> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
>> >>> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
>> >>> +
>> >>> +}
>> >>> +
>> >>>   static void
>> >>>   nbctl_lsp_set_type(struct ctl_context *ctx)
>> >>>   {
>> >>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct
>> >>> ctl_context *ctx)
>> >>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
>> >>>   }
>> >>>
>> >>> +static char * OVS_WARN_UNUSED_RESULT
>> >>> +parse_filter(const char *arg, const char **selection_p)
>> >>> +{
>> >>> +    /* Validate selection.  Only require the first letter. */
>> >>> +    if (arg[0] == 't') {
>> >>> +        *selection_p = "to-lport";
>> >>> +    } else if (arg[0] == 'f') {
>> >>> +        *selection_p = "from-lport";
>> >>> +    } else if (arg[0] == 'b') {
>> >>> +        *selection_p = "both";
>> >>> +    } else {
>> >>> +        *selection_p = NULL;
>> >>> +        return xasprintf("%s: selection must be \"to-lport\" or "
>> >>> +                         "\"from-lport\" or \"both\" ", arg);
>> >>> +    }
>> >>> +    return NULL;
>> >>> +}
>> >>> +
>> >>> +static char * OVS_WARN_UNUSED_RESULT
>> >>> +parse_type(const char *arg, const char **type_p)
>> >>> +{
>> >>> +    /* Validate type.  Only require the first letter. */
>> >>> +    if (arg[0] == 'g') {
>> >>> +        *type_p = "gre";
>> >>> +    } else if (arg[0] == 'e') {
>> >>> +        *type_p = "erspan";
>> >>> +    } else {
>> >>> +        *type_p = NULL;
>> >>> +        return xasprintf("%s: type must be \"gre\" or "
>> >>> +                         "\"erspan\"", arg);
>> >>> +    }
>> >>> +    return NULL;
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
>> >>> +{
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_mirror_add(struct ctl_context *ctx)
>> >>> +{
>> >>> +    const char *filter = NULL;
>> >>> +    const char *sink_ip = NULL;
>> >>> +    const char *type = NULL;
>> >>> +    const char *name = NULL;
>> >>> +    char *new_sink_ip = NULL;
>> >>> +    int64_t index;
>> >>> +    char *error = NULL;
>> >>> +    const struct nbrec_mirror *mirror_check = NULL;
>> >>> +
>> >>> +    /* Mirror Name */
>> >>> +    name = ctx->argv[1];
>> >>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>> >>> +        if (!strcmp(mirror_check->name, name)) {
>> >>> +            ctl_error(ctx, "Mirror with %s name already exists.",
>> >>> +                      name);
>> >>> +            return;
>> >>> +        }
>> >>> +    }
>> >>> +
>> >>> +    /* Tunnel Type - GRE/ERSPAN */
>> >>> +    error = parse_type(ctx->argv[2], &type);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +    /* tunnel index / GRE key / ERSPAN idx */
>> >>> +    index = atoi(ctx->argv[3]);
>> >> Shouldn't we validate the input is an actual number?
>> >>
>> >>> +
>> >>> +    /* Filter for mirroring */
>> >>> +    error = parse_filter(ctx->argv[4], &filter);
>> >>> +    if (error) {
>> >>> +        ctx->error = error;
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +    /* Destination / Sink details */
>> >>> +    sink_ip = ctx->argv[5];
>> >>> +
>> >>> +    /* check if it is a valid ip */
>> >>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>> >>> +    if (!new_sink_ip) {
>> >>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>> >>> +    }
>> >>> +
>> >>> +    if (new_sink_ip) {
>> >>> +        free(new_sink_ip);
>> >>> +    } else {
>> >>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +    /* Create the mirror. */
>> >>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>> >>> +    nbrec_mirror_set_name(mirror, name);
>> >>> +    nbrec_mirror_set_index(mirror, index);
>> >>> +    nbrec_mirror_set_filter(mirror, filter);
>> >>> +    nbrec_mirror_set_type(mirror, type);
>> >>> +    nbrec_mirror_set_sink(mirror, sink_ip);
>> >>> +
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
>> >>> +{
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_mirror_del(struct ctl_context *ctx)
>> >>> +{
>> >>> +    const struct nbrec_mirror *mirror, *next;
>> >>> +
>> >>> +    /* If a name is not specified, delete all mirrors. */
>> >>> +    if (ctx->argc == 1) {
>> >>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>> >>> +            nbrec_mirror_delete(mirror);
>> >>> +        }
>> >>> +        return;
>> >>> +    }
>> >>> +
>> >>> +    /* Remove the matching mirror. */
>> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> >>> +        if (strcmp(ctx->argv[1], mirror->name)) {
>> >>> +            continue;
>> >>> +        }
>> >>> +        nbrec_mirror_delete(mirror);
>> >>> +        return;
>> >>> +    }
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
>> >>> +{
>> >>> +    ovsdb_idl_add_column(ctx->idl,
>> >>> &nbrec_logical_switch_port_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> >>> +}
>> >>> +
>> >>> +static void
>> >>> +nbctl_mirror_list(struct ctl_context *ctx)
>> >>> +{
>> >>> +
>> >>> +    const struct nbrec_mirror **mirrors = NULL;
>> >>> +    const struct nbrec_mirror *mirror;
>> >>> +    size_t n_capacity = 0;
>> >>> +    size_t n_mirrors = 0;
>> >>> +
>> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> >>> +        if (n_mirrors == n_capacity) {
>> >>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof
>> >>> *mirrors);
>> >>> +        }
>> >>> +
>> >>> +        mirrors[n_mirrors] = mirror;
>> >>> +        n_mirrors++;
>> >>> +    }
>> >>> +
>> >>> +    if (n_mirrors) {
>> >>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>> >>> +    }
>> >>> +
>> >>> +    for (size_t i = 0; i < n_mirrors; i++) {
>> >>> +        mirror = mirrors[i];
>> >>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>> >>> +        /* print all the values */
>> >>> +        ds_put_format(&ctx->output, "  Type     : %s\n",
>> >>> mirror->type);
>> >>> +        ds_put_format(&ctx->output, "  Sink     : %s\n",
>> >>> mirror->sink);
>> >>> +        ds_put_format(&ctx->output, "  Filter   : %s\n",
>> >>> mirror->filter);
>> >>> +        ds_put_format(&ctx->output, "  Index/Key: %ld\n",
>> >>> +                                                (long int)
>> >>> mirror->index);
>> >> You don't ned to cast if you pass %d formatter instead of %ld. The
>> >> same applies to other places in the patch where you cast to long int.
>> >> In general, casting is not needed and should be avoided.
>> >>
>> >>> + ds_put_cstr(&ctx->output,   "  Sources  :");
>> >>> +        if (mirror->n_src > 0) {
>> >>> +            struct svec srcs;
>> >>> +            const char *src;
>> >>> +            size_t j;
>> >>> +            svec_init(&srcs);
>> >>> +            for (j = 0; j < mirror->n_src; j++) {
>> >>> +                svec_add(&srcs, mirror->src[j]->name);
>> >>> +            }
>> >>> +            svec_sort(&srcs);
>> >>> +            SVEC_FOR_EACH (j, src, &srcs) {
>> >>> +                ds_put_format(&ctx->output, "  %s", src);
>> >>> +            }
>> >>> +            svec_destroy(&srcs);
>> >>> +        } else {
>> >>> +            ds_put_cstr(&ctx->output, "  None attached");
>> >>> +        }
>> >>> +        ds_put_cstr(&ctx->output, "\n");
>> >>> +    }
>> >>> +
>> >>> +    free(mirrors);
>> >>> +}
>> >>> +
>> >>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>> >>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
>> >>>       = {{&nbrec_logical_switch_port_col_name, NULL,
>> >>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax
>> >>> nbctl_commands[] = {
>> >>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list,
>> nbctl_qos_list,
>> >>>         NULL, "", RO },
>> >>>
>> >>> +    /* mirror commands. */
>> >>> +    { "mirror-add", 5, 5,
>> >>> +      "NAME TYPE INDEX FILTER IP",
>> >>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist",
>> >>> RW },
>> >>> +    { "mirror-del", 0, 1, "[NAME]",
>> >>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>> >>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list,
>> >>> nbctl_mirror_list,
>> >>> +      NULL, "", RO },
>> >>> +
>> >>>       /* meter commands. */
>> >>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
>> >>> nbctl_pre_meter_add,
>> >>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>> >>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax
>> >>> nbctl_commands[] = {
>> >>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>> >>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
>> >>> nbctl_lsp_get_ls,
>> >>>         NULL, "", RO },
>> >>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> >>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
>> >>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> >>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>> >>>
>> >>>       /* forwarding group commands. */
>> >>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
>> >>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>> >>> index f60dde1b6..3d73e9e25 100644
>> >>> --- a/utilities/ovn-sbctl.c
>> >>> +++ b/utilities/ovn-sbctl.c
>> >>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>> >>>       ovsdb_idl_add_column(ctx->idl,
>> &sbrec_port_binding_col_datapath);
>> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>> >>> +    ovsdb_idl_add_column(ctx->idl,
>> >>> &sbrec_port_binding_col_mirror_rules);
>> >>>
>> >>>       ovsdb_idl_add_column(ctx->idl,
>> >>> &sbrec_logical_flow_col_logical_datapath);
>> >>>       ovsdb_idl_add_column(ctx->idl,
>> >>> &sbrec_logical_flow_col_logical_dp_group);
>> >>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class
>> >>> tables[SBREC_N_TABLES] = {
>> >>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>> >>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>> >>>
>> >>> +    [SBREC_TABLE_MIRROR].row_ids[0]
>> >>> +    = {&sbrec_mirror_col_name, NULL, NULL},
>> >>> +
>> >>>       [SBREC_TABLE_METER].row_ids[0]
>> >>>       = {&sbrec_meter_col_name, NULL, NULL},
>> >>>
>> >>> --
>> >>> 2.31.1
>> >>>
>>
>>
Ihar Hrachyshka Nov. 16, 2022, 2:31 p.m. UTC | #7
On 11/16/22 9:11 AM, Abhiram R N wrote:
> Hi Ihar,
>
>
> On Wed, Nov 16, 2022 at 7:01 PM Ihar Hrachyshka <ihrachys@redhat.com> 
> wrote:
>
>     On 11/16/22 8:08 AM, Abhiram R N wrote:
>>     Hi Ihar,
>>
>>     Thanks for your inputs. I think I have found the issue in the test.
>>     In the test case I had missed one thing. Below are the details
>>
>>     The log which you shared as the last message in ovs-vswitchd log
>>     led me to it.
>>
>>     > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max
>>     > translation depth 64 on bridge br-int while processing >
>>     arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>>
>>     What I had missed is, the creation of hv2, setting ip to br-phys
>>     and ARP resolving. (See code at the end for what I have added).
>>     Once I added this now everything is passing fine. I don't see any
>>     failures.
>>
>>     Also everything works in a single test case. So, I will revert
>>     back to the old method of having 1 bulk updates test case and
>>     verify all cases there and with below code, will submit a patch soon.
>>
>>     Since in the bulk updates test case we were not verifying the
>>     actual packet flow (already covered in a separate test) I had
>>     thought the below code was not needed.
>>     But that wasn't the case!
>>
>>     FYI, what I added now...
>>
>>     net_add n2
>>
>>     sim_add hv2
>>     as hv2
>>     ovs-vsctl add-br br-phys -- set bridge br-phys
>>     other-config:hwaddr=\"00:00:00:02:02:00\"
>>     ovn_attach n2 br-phys 192.168.1.12
>>
>>     OVN_POPULATE_ARP
>
>
>     While this change to the test case may fix the test failure, I
>     don't think we should just ignore the fact that any test case
>     creating a number of mirrors in ovsdb may put vswitchd into a 100%
>     cpu spin loop that effectively freezes all processing. We'll need
>     to understand what happens to vswitchd with the original test case.
>
>
> Yeah sure, we can wait for ILya's response.
> But shall go ahead and push the ovn.at <http://ovn.at> change? Because 
> that is a valid change and required anyways I feel. And will clean up 
> unnecessary multiple test cases as we can just have one for all bulk 
> update cases. Please let me know.
>
> In Addition to the scenario you have mentioned earlier, I got based on 
> the failing test case below questions
> More specifically this issue happens when multiple mirrors are created 
> with an output port and that output port has a remote_ip set which is 
> non-existent . (possibly is it a wrong configuration?). (What is the 
> expected behaviour?)
> This probably should be the same even today on OVS. I would say it has 
> nothing to do with configuring from OVN!.
>

Agreed it's not OVN specific and so you should be able to proceed with 
your updates to test suite (merging the test cases). I will in the 
meantime review the existing v13 version of the patch; once v14 is 
available, we can compare the difference between the two versions to 
speed things up. As discussed, I'll post the final code review today.


Assuming there's nothing wrong with the OVN implementation, we should be 
able to proceed with it independently of the OVS mirror handling bug 
that is clearly present.


> Thanks & Regards,
> Abhiram R N
>
>>
>>     Thanks & Regards,
>>     Abhiram R N
>>
>>     On Wed, Nov 16, 2022 at 4:39 AM Ihar Hrachyshka
>>     <ihrachys@redhat.com> wrote:
>>
>>         On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
>>         > I think there's a problem with the bulk tests added in this
>>         patch. I
>>         > will cover this issue in this email, and I'll send my code
>>         review
>>         > tomorrow as promised, since it's getting late here.
>>         >
>>         >
>>         > When running the whole suite locally, I get the following
>>         failures:
>>         >
>>         >
>>         > 401: Mirror test bulk swap attachments -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16425 <http://ovn.at:16425>)
>>         > 402: Mirror test bulk swap attachments -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=no FAILED
>>         (ovn.at:16425 <http://ovn.at:16425>)
>>         > 403: Mirror test bulk attach multiple -- ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16537 <http://ovn.at:16537>)
>>         > 410: Mirror test bulk more detach and less attach --
>>         ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=no ok
>>         > 412: Mirror test bulk attach more than detach -- ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=no ok
>>         > 416: Mirror test bulk detach multiple -- ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=no ok
>>         > 408: Mirror test bulk more detach and less attach --
>>         ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=no FAILED
>>         (ovn.at:16650 <http://ovn.at:16650>)
>>         > 417: Mirror test bulk detach multiple -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=yes ok
>>         > 409: Mirror test bulk more detach and less attach --
>>         ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16650 <http://ovn.at:16650>)
>>         > 411: Mirror test bulk attach more than detach -- ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16766 <http://ovn.at:16766>)
>>         > 418: Mirror test bulk detach multiple -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=no ok
>>         > 413: Mirror test bulk attach more than detach -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16766 <http://ovn.at:16766>)
>>         > 415: Mirror test bulk detach multiple -- ovn-northd --
>>         > parallelization=yes -- ovn_monitor_all=yes FAILED
>>         (ovn.at:16879 <http://ovn.at:16879>)
>>         > 414: Mirror test bulk attach more than detach -- ovn-northd --
>>         > parallelization=no -- ovn_monitor_all=no FAILED
>>         (ovn.at:16766 <http://ovn.at:16766>)
>>         >
>>         >
>>         > Note that some of the test case variants passed, and I
>>         don't think
>>         > there's a clear pattern as to which of variants in the test
>>         matrix do
>>         > fail.
>>         >
>>         >
>>         > The error that triggers the failure is during ovs-vswitchd
>>         cleanup:
>>         >
>>         >
>>         > ./ovn.at:16425 <http://ovn.at:16425>: ovs-appctl
>>         --timeout=10 -t ovs-vswitchd exit --cleanup
>>         > --- /dev/null   2022-11-04 04:09:25.869645998 +0000
>>         > +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr
>>         > 2022-11-15 16:31:15.557479369 +0000
>>         > @@ -0,0 +1,2 @@
>>         > +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating
>>         with signal
>>         > 14 (Alarm clock)
>>         >
>>         +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source:
>>         line
>>         > 282: 1033659 Alarm clock ovs-appctl --timeout=10 -t
>>         > ovs-vswitchd exit --cleanup
>>         > ./ovn.at:16425 <http://ovn.at:16425>: exit code was 142,
>>         expected 0
>>         >
>>         >
>>         > The very last message in ovs-vswitchd log on hv1 is exactly
>>         10 seconds
>>         > before the alarm clock error:
>>         >
>>         >
>>         > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over
>>         max
>>         > translation depth 64 on bridge br-int while processing
>>         >
>>         arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
>>         >
>>         >
>>         > I don't see coredumps generated for any of test processes,
>>         so it's
>>         > probably not the case of ovs-vswitchd crashing on exit request.
>>         >
>>         >
>>         > I tried to adjust your test cases to a minimal reproducer
>>         and I found
>>         > that if a test case creates two mirrors, both of to-lport
>>         type, then
>>         > ovs-vswitchd freezes (?) - f.e. it no longer responds to
>>         appctl
>>         > requests, nor it handles new ports. But if I merely change
>>         the type of
>>         > one of mirrors in the test to from-lport, the test passes.
>>         >
>>         >
>>         > On the other hand, a consistent way to trigger the failure
>>         is adding a
>>         > 'sleep 3' at the end of a test case just before cleanup,
>>         apparently to
>>         > allow vswitchd to catch on the mirror updates and lock
>>         somewhere in
>>         > the code. I see vswitchd spinning at ~100% cpu in 'top'
>>         output when it
>>         > gets into this state. It's clearly doing SOMETHING, not
>>         just sleeping. :)
>>         >
>>         >
>>         > I suspect there's some bug inside vswitchd that makes it
>>         lock / spin
>>         > for a particular setup of mirrors. Whatever OVN sets up in
>>         vswitchd
>>         > database, the latter should not freeze. It would be helpful
>>         to provide
>>         > a short ovs-only reproducer for the situation that would
>>         not involve
>>         > OVN so that our OVS friends can take a look.
>>         >
>>         >
>>         > For the record, the mirrors in ovsdb are:
>>         >
>>         >
>>         > _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
>>         > external_ids        : {}
>>         > name                : mirror0
>>         > output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
>>         > output_vlan         : []
>>         > select_all          : false
>>         > select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab,
>>         > 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
>>         > select_src_port     : []
>>         > select_vlan         : []
>>         > snaplen             : []
>>         > statistics          : {}
>>         >
>>         > _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
>>         > external_ids        : {}
>>         > name                : mirror1
>>         > output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
>>         > output_vlan         : []
>>         > select_all          : false
>>         > select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6,
>>         > 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
>>         > select_src_port     : []
>>         > select_vlan         : []
>>         > snaplen             : []
>>         > statistics          : {}
>>         >
>>         >
>>         > Bridge output here:
>>         >
>>         >
>>         > 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
>>         >     Bridge br-int
>>         >         fail_mode: secure
>>         >         datapath_type: system
>>         >         Port vif4
>>         >             Interface vif4
>>         >         Port vif2
>>         >             Interface vif2
>>         >         Port br-int
>>         >             Interface br-int
>>         >                 type: internal
>>         >         Port vif1
>>         >             Interface vif1
>>         >         Port ovn-mirror0
>>         >             Interface ovn-mirror0
>>         >                 type: gre
>>         >                 options: {key="0", remote_ip="192.168.1.12"}
>>         >         Port vif3
>>         >             Interface vif3
>>         >         Port ovn-mirror1
>>         >             Interface ovn-mirror1
>>         >                 type: gre
>>         >                 options: {key="1", remote_ip="192.168.1.12"}
>>         >         Port patch-br-int-to-ln-public
>>         >             Interface patch-br-int-to-ln-public
>>         >                 type: patch
>>         >                 options: {peer=patch-ln-public-to-br-int}
>>         >     Bridge br-phys
>>         >         Port br-phys
>>         >             Interface br-phys
>>         >                 type: internal
>>         >                 options:
>>         >
>>         {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap",
>>
>>         >
>>         tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
>>         >         Port br-phys_n1
>>         >             Interface br-phys_n1
>>         >                 options:
>>         >
>>         {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap",
>>
>>         >
>>         stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock",
>>
>>         >
>>         tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
>>
>>         >
>>         >         Port patch-ln-public-to-br-int
>>         >             Interface patch-ln-public-to-br-int
>>         >                 type: patch
>>         >                 options: {peer=patch-br-int-to-ln-public}
>>         >
>>         Perhaps this may be of relevance: when I attach gdb to
>>         vswitchd process,
>>         I can see a never-ending stack of calls in thread 1 that
>>         looks like:
>>
>>         #0  0x0000000000406760 in memcpy@plt ()
>>         #1  0x0000000000499d88 in dp_packet_clone_with_headroom
>>         (buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
>>         #2  0x000000000049bd4d in dp_packet_batch_clone
>>         (dst=0x7fffcbf54740,
>>         src=0x7fffcbf55230) at lib/dp-packet.h:863
>>         #3  0x00000000004b15ef in dp_execute_output_action
>>         (pmd=0x7f638115f010,
>>         packets_=0x7fffcbf55230, should_steal=false, port_no=3) at
>>         lib/dpif-netdev.c:8696
>>         #4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200,
>>         packets_=0x7fffcbf55230, a=0x7fffcbf555c0,
>>         should_steal=false) at
>>         lib/dpif-netdev.c:8787
>>         #5  0x0000000000507834 in odp_execute_actions
>>         (dp=0x7fffcbf55200,
>>         batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578,
>>         actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
>>              at lib/odp-execute.c:993
>>         #6  0x00000000004b251e in dp_netdev_execute_actions
>>         (pmd=0x7f638115f010,
>>         packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60,
>>         actions=0x7fffcbf55578, actions_len=88)
>>              at lib/dpif-netdev.c:9105
>>         #7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630,
>>         execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
>>         #8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630,
>>         ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>>         lib/dpif-netdev.c:4606
>>         #9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630,
>>         ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
>>         lib/dpif.c:1372
>>         #10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630,
>>         execute=0x7fffcbf55500) at lib/dpif.c:1326
>>         #11 0x000000000043e46d in ofproto_dpif_execute_actions__
>>         (ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0,
>>         ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
>>              packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
>>         #12 0x0000000000467e8a in compose_table_xlate
>>         (ctx=0x7fffcbf5f240,
>>         out_dev=0x1879430, packet=0x7fffcbf56050) at
>>         ofproto/ofproto-dpif-xlate.c:3526
>>         #13 0x0000000000468020 in tnl_send_arp_request
>>         (ctx=0x7fffcbf5f240,
>>         out_dev=0x1879430, eth_src=..., ip_src=184658112,
>>         ip_dst=201435328) at
>>         ofproto/ofproto-dpif-xlate.c:3555
>>         #14 0x0000000000468710 in native_tunnel_output
>>         (ctx=0x7fffcbf5f240,
>>         xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7,
>>         truncate=false,
>>         is_last_action=false)
>>              at ofproto/ofproto-dpif-xlate.c:3721
>>         #15 0x000000000046a78e in compose_output_action__
>>         (ctx=0x7fffcbf5f240,
>>         ofp_port=7, xr=0x0, check_stp=true, is_last_action=false,
>>         truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
>>         #16 0x000000000046aa38 in compose_output_action
>>         (ctx=0x7fffcbf5f240,
>>         ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at
>>         ofproto/ofproto-dpif-xlate.c:4416
>>         #17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240,
>>         out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at
>>         ofproto/ofproto-dpif-xlate.c:2533
>>         #18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240,
>>         xbundle=0x1880d00, mirrors=2) at
>>         ofproto/ofproto-dpif-xlate.c:2190
>>         #19 0x000000000046a96b in compose_output_action__
>>         (ctx=0x7fffcbf5f240,
>>         ofp_port=1, xr=0x0, check_stp=true, is_last_action=false,
>>         truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
>>         #20 0x000000000046aa38 in compose_output_action
>>         (ctx=0x7fffcbf5f240,
>>         ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at
>>         ofproto/ofproto-dpif-xlate.c:4416
>>         #21 0x000000000046cf3e in xlate_output_action
>>         (ctx=0x7fffcbf5f240,
>>         port=1, controller_len=0, may_packet_in=true,
>>         is_last_action=false,
>>         truncate=false, group_bucket_action=false)
>>              at ofproto/ofproto-dpif-xlate.c:5361
>>         #22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638,
>>         ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>>         group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
>>         #23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>>         rule=0x1860480, deepens=false, is_last_action=false,
>>         actions_xlator=0x470caf <do_xlate_actions>)
>>              at ofproto/ofproto-dpif-xlate.c:4439
>>         #24 0x000000000046b0de in xlate_table_action
>>         (ctx=0x7fffcbf5f240,
>>         in_port=5, table_id=65 'A', may_packet_in=false,
>>         honor_table_miss=false,
>>         with_ct_orig=false, is_last_action=false,
>>              xlator=0x470caf <do_xlate_actions>) at
>>         ofproto/ofproto-dpif-xlate.c:4568
>>         #25 0x000000000046bbd3 in xlate_ofpact_resubmit
>>         (ctx=0x7fffcbf5f240,
>>         resubmit=0x185cfb8, is_last_action=false) at
>>         ofproto/ofproto-dpif-xlate.c:4879
>>         #26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8,
>>         ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>>         group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>>         #27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
>>         rule=0x185ce00, deepens=false, is_last_action=false,
>>         actions_xlator=0x470caf <do_xlate_actions>)
>>              at ofproto/ofproto-dpif-xlate.c:4439
>>         #28 0x000000000046b0de in xlate_table_action
>>         (ctx=0x7fffcbf5f240,
>>         in_port=5, table_id=64 '@', may_packet_in=false,
>>         honor_table_miss=false,
>>         with_ct_orig=false, is_last_action=false,
>>              xlator=0x470caf <do_xlate_actions>) at
>>         ofproto/ofproto-dpif-xlate.c:4568
>>         #29 0x000000000046bbd3 in xlate_ofpact_resubmit
>>         (ctx=0x7fffcbf5f240,
>>         resubmit=0x1834458, is_last_action=false) at
>>         ofproto/ofproto-dpif-xlate.c:4879
>>         #30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458,
>>         ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
>>         group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>>
>>         it goes like that over and over and over for hundreds if not
>>         thousands
>>         of calls down the stack until
>>
>>         #5738 0x000000000046b0de in xlate_table_action
>>         (ctx=0x7fffcc09b330,
>>         in_port=65533, table_id=9 '\t', may_packet_in=false,
>>         honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>>         xlator=0x470caf <do_xlate_actions>) at
>>         ofproto/ofproto-dpif-xlate.c:4568
>>         #5739 0x000000000046bbd3 in xlate_ofpact_resubmit
>>         (ctx=0x7fffcc09b330,
>>         resubmit=0x1831ac0, is_last_action=true) at
>>         ofproto/ofproto-dpif-xlate.c:4879
>>         #5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68,
>>         ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true,
>>         group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>>         #5741 0x000000000046ab3b in xlate_recursively
>>         (ctx=0x7fffcc09b330,
>>         rule=0x18318b0, deepens=false, is_last_action=true,
>>         actions_xlator=0x470caf <do_xlate_actions>) at
>>         ofproto/ofproto-dpif-xlate.c:4439
>>         #5742 0x000000000046b0de in xlate_table_action
>>         (ctx=0x7fffcc09b330,
>>         in_port=65533, table_id=8 '\b', may_packet_in=false,
>>         honor_table_miss=false, with_ct_orig=false, is_last_action=true,
>>         xlator=0x470caf <do_xlate_actions>) at
>>         ofproto/ofproto-dpif-xlate.c:4568
>>         #5743 0x000000000046bbd3 in xlate_ofpact_resubmit
>>         (ctx=0x7fffcc09b330,
>>         resubmit=0x187bcc0, is_last_action=true) at
>>         ofproto/ofproto-dpif-xlate.c:4879
>>         #5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80,
>>         ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true,
>>         group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>>         #5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0,
>>         xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
>>         #5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0,
>>         opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
>>         #5747 0x00000000004250d7 in ofproto_packet_out_start
>>         (ofproto=0x17c10e0,
>>         opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
>>         #5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40,
>>         oh=0x17fdfa0) at ofproto/ofproto.c:3764
>>         #5749 0x000000000042fce2 in handle_single_part_openflow
>>         (ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at
>>         ofproto/ofproto.c:8664
>>         #5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40,
>>         msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
>>         #5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40,
>>         handle_openflow=0x430072 <handle_openflow>) at
>>         ofproto/connmgr.c:1329
>>         #5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0,
>>         handle_openflow=0x430072 <handle_openflow>) at
>>         ofproto/connmgr.c:356
>>         #5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at
>>         ofproto/ofproto.c:1933
>>         #5754 0x00000000004101d4 in bridge_run__ () at
>>         vswitchd/bridge.c:3210
>>         #5755 0x00000000004103d3 in bridge_run () at
>>         vswitchd/bridge.c:3269
>>         #5756 0x0000000000415cf0 in main (argc=10,
>>         argv=0x7fffcc09dff8) at
>>         vswitchd/ovs-vswitchd.c:129
>>
>>         The main thread never getting out of some processing code to
>>         reach any
>>         other handlers (e.g. for appctl requests?)
>>
>>         I'm adding Ilya to CC in case he has an idea why vswitchd
>>         could lock /
>>         freeze / spin indefinitely on two to-lport mirror creation.
>>
>>
>>         > On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
>>         >> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N
>>         <abhiramrn@gmail.com> wrote:
>>         >>> Mirror creation just creates the mirror. The
>>         lsp-attach-mirror
>>         >>> triggers the sequence to create Mirror in OVS DB on
>>         compute node.
>>         >>> OVS already supports Port Mirroring.
>>         >>>
>>         >>> Note: This is targeted to mirror to destinations anywhere
>>         outside the
>>         >>> cluster where the analyser resides and it need not be an
>>         OVN node.
>>         >>>
>>         >>> Example commands are as below:
>>         >>>
>>         >>> Mirror creation
>>         >>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>>         >>>
>>         >>> Attach a logical port to the mirror.
>>         >>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>>         >>>
>>         >>> Detach a source from Mirror
>>         >>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>>         >>>
>>         >>> Mirror deletion
>>         >>> ovn-nbctl mirror-del mirror1
>>         >>>
>>         >>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>>         >>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
>>         >>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
>>         >>> ---
>>         >>> v12 --> V13: Made each of bulk test cases(in ovn.at
>>         <http://ovn.at>) as separate
>>         >>>               test to make it pass consistently.
>>         >>> V11 --> V12: Minor fix in ovn.at <http://ovn.at> to solve
>>         intermittent failures
>>         >>>
>>         >>> V10 --> V11: Addressed review comments from V10 by Ihar
>>         >>>             i) Expanded bulk updates test cases in ovn.at
>>         <http://ovn.at>
>>         >>>                Overall below cases are covered
>>         >>>                a) Attaches multiple mirrors (new)
>>         >>>                b) Equal detaches and attaches (same as V10)
>>         >>>                c) Detaches more than attaches (new)
>>         >>>                d) Attaches more than detaches (new)
>>         >>>                e) Detaches all (new)
>>         >>>            ii) Addressed the detach all case in mirror.c
>>         >>>           iii) Minor correction in NEWS
>>         >>>            iv) Added invalid mirror attach case in
>>         ovn-nbctl.at <http://ovn-nbctl.at>
>>         >>>
>>         >>> Files modified (V10 --> V11):
>>         >>> Code --> mirror.c
>>         >>> Test --> ovn.at <http://ovn.at>, ovn-nbctl.at
>>         <http://ovn-nbctl.at>
>>         >>> Misc --> NEWS
>>         >>>
>>         >>> Ihar,
>>         >>>      Regarding mirror_delete function param delete_all it
>>         is wrt the
>>         >>> port binding and if a port binding is removed we delete
>>         all its
>>         >>> attachment. Already that use case is covered in ovn.at
>>         <http://ovn.at>.
>>         >>> Having said that the detaches all had issue in
>>         mirror_delete which
>>         >>> I have addressed. With all the above cases added now in
>>         bulk updates
>>         >>> hope it should give good assurance.
>>         >>>
>>         >>>   NEWS                        |   1 +
>>         >>>   controller/automake.mk <http://automake.mk> |   4 +-
>>         >>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>>         >>>   controller/mirror.h         |  53 +++
>>         >>>   controller/ovn-controller.c | 266 ++++++++++--
>>         >>>   northd/en-northd.c          |   4 +
>>         >>>   northd/inc-proc-northd.c    |   4 +
>>         >>>   northd/northd.c             | 172 ++++++++
>>         >>>   northd/northd.h             |   2 +
>>         >>>   ovn-nb.ovsschema            |  31 +-
>>         >>>   ovn-nb.xml                  |  63 +++
>>         >>>   ovn-sb.ovsschema            |  26 +-
>>         >>>   ovn-sb.xml                  |  50 +++
>>         >>>   tests/ovn-nbctl.at <http://ovn-nbctl.at> | 120 ++++++
>>         >>>   tests/ovn-northd.at <http://ovn-northd.at> | 102 +++++
>>         >>>   tests/ovn.at <http://ovn.at> | 778
>>         >>> ++++++++++++++++++++++++++++++++++++
>>         >>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>>         >>>   utilities/ovn-sbctl.c       |   4 +
>>         >>>   18 files changed, 2547 insertions(+), 28 deletions(-)
>>         >>>   create mode 100644 controller/mirror.c
>>         >>>   create mode 100644 controller/mirror.h
>>         >>>
>>         >>> diff --git a/NEWS b/NEWS
>>         >>> index 224a7b83e..84b22abdb 100644
>>         >>> --- a/NEWS
>>         >>> +++ b/NEWS
>>         >>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>>         >>>       any of LR's LRP IP, there is no need to create SNAT
>>         entry.
>>         >>> Now such
>>         >>>       traffic destined to LRP IP is not dropped.
>>         >>>     - Bump python version required for building OVN to 3.6.
>>         >>> +  - Added Support for Remote Port Mirroring.
>>         >>>
>>         >>>   OVN v22.06.0 - 03 Jun 2022
>>         >>>   --------------------------
>>         >>> diff --git a/controller/automake.mk <http://automake.mk>
>>         b/controller/automake.mk <http://automake.mk>
>>         >>> index c2ab1bbe6..334672b4d 100644
>>         >>> --- a/controller/automake.mk <http://automake.mk>
>>         >>> +++ b/controller/automake.mk <http://automake.mk>
>>         >>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>>         >>>          controller/ovsport.h \
>>         >>>          controller/ovsport.c \
>>         >>>          controller/vif-plug.h \
>>         >>> -       controller/vif-plug.c
>>         >>> +       controller/vif-plug.c \
>>         >>> +       controller/mirror.h \
>>         >>> +       controller/mirror.c
>>         >>>
>>         >>>   controller_ovn_controller_LDADD = lib/libovn.la
>>         <http://libovn.la>
>>         >>> $(OVS_LIBDIR)/libopenvswitch.la <http://libopenvswitch.la>
>>         >>>   man_MANS += controller/ovn-controller.8
>>         >>> diff --git a/controller/mirror.c b/controller/mirror.c
>>         >>> new file mode 100644
>>         >>> index 000000000..11f2b63a6
>>         >>> --- /dev/null
>>         >>> +++ b/controller/mirror.c
>>         >>> @@ -0,0 +1,538 @@
>>         >>> +/* Copyright (c) 2022 Red Hat, Inc.
>>         >>> + *
>>         >>> + * Licensed under the Apache License, Version 2.0 (the
>>         "License");
>>         >>> + * you may not use this file except in compliance with
>>         the License.
>>         >>> + * You may obtain a copy of the License at:
>>         >>> + *
>>         >>> + * http://www.apache.org/licenses/LICENSE-2.0
>>         >>> + *
>>         >>> + * Unless required by applicable law or agreed to in
>>         writing, software
>>         >>> + * distributed under the License is distributed on an
>>         "AS IS" BASIS,
>>         >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>>         express or
>>         >>> implied.
>>         >>> + * See the License for the specific language governing
>>         permissions and
>>         >>> + * limitations under the License.
>>         >>> + */
>>         >>> +
>>         >>> +#include <config.h>
>>         >>> +#include <unistd.h>
>>         >>> +
>>         >>> +/* library headers */
>>         >>> +#include "lib/sset.h"
>>         >>> +#include "lib/util.h"
>>         >>> +
>>         >>> +/* OVS includes. */
>>         >>> +#include "lib/vswitch-idl.h"
>>         >>> +#include "openvswitch/vlog.h"
>>         >>> +
>>         >>> +/* OVN includes. */
>>         >>> +#include "binding.h"
>>         >>> +#include "lib/ovn-sb-idl.h"
>>         >>> +#include "mirror.h"
>>         >>> +
>>         >>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
>>         >>> +
>>         >>> +/* Static function declarations */
>>         >>> +
>>         >>> +static const struct ovsrec_port *
>>         >>> +get_port_for_iface(const struct ovsrec_interface *iface,
>>         >>> +                  const struct ovsrec_bridge *br_int)
>>         >>> +{
>>         >>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
>>         >>> +        const struct ovsrec_port *p = br_int->ports[i];
>>         >>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
>>         >>> +            if (!strcmp(iface->name,
>>         p->interfaces[j]->name)) {
>>         >>> +                return p;
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +    return NULL;
>>         >>> +}
>>         >>> +
>>         >>> +static bool
>>         >>> +mirror_create(const struct sbrec_port_binding *pb,
>>         >>> +              struct port_mirror_ctx *pm_ctx)
>>         >>> +{
>>         >>> +    const struct ovsrec_mirror *mirror = NULL;
>>         >>> +
>>         >>> +    if (pb->n_up && !pb->up[0]) {
>>         >>> +        return true;
>>         >>> +    }
>>         >>> +
>>         >>> +    if (pb->chassis != pm_ctx->chassis_rec) {
>>         >>> +        return true;
>>         >>> +    }
>>         >>> +
>>         >>> +    if (!pm_ctx->ovs_idl_txn) {
>>         >>> +        return false;
>>         >>> +    }
>>         >>> +
>>         >>> +
>>         >>> +    VLOG_INFO("Mirror rule(s) present for %s ",
>>         pb->logical_port);
>>         >>> +    /* Loop through the mirror rules */
>>         >>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>>         >>> +        /* check if the mirror already exists in OVS DB */
>>         >>> +        bool create_mirror = true;
>>         >>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror,
>>         pm_ctx->mirror_table) {
>>         >>> +            if (!strcmp(pb->mirror_rules[i]->name,
>>         mirror->name)) {
>>         >>> +                /* Mirror with same name already exists
>>         >>> +                 * No need to create mirror
>>         >>> +                 */
>>         >>> +                create_mirror = false;
>>         >>> +                break;
>>         >>> +            }
>>         >>> +        }
>>         >>> +
>>         >>> +        if (create_mirror) {
>>         >>> +
>>         >>> +            struct smap options =
>>         SMAP_INITIALIZER(&options);
>>         >>> +            char *port_name, *key;
>>         >>> +
>>         >>> +            key = xasprintf("%ld",(long int)
>>         >>> pb->mirror_rules[i]->index);
>>         >>> +            smap_add(&options, "remote_ip",
>>         >>> pb->mirror_rules[i]->sink);
>>         >>> +            smap_add(&options, "key", key);
>>         >>> +            if (!strcmp(pb->mirror_rules[i]->type,
>>         "erspan")) {
>>         >>> +                /* Set the ERSPAN index */
>>         >>> + smap_add(&options, "erspan_idx", key);
>>         >>> + smap_add(&options, "erspan_ver","1");
>>         >>> +
>>         >>> +            }
>>         >>> +            struct ovsrec_interface *iface =
>>         >>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>>         >>> +            port_name = xasprintf("ovn-%s",
>>         >>> + pb->mirror_rules[i]->name);
>>         >>> +
>>         >>> + ovsrec_interface_set_name(iface, port_name);
>>         >>> + ovsrec_interface_set_type(iface,
>>         >>> pb->mirror_rules[i]->type);
>>         >>> + ovsrec_interface_set_options(iface, &options);
>>         >>> +
>>         >>> +            struct ovsrec_port *port =
>>         >>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>>         >>> + ovsrec_port_set_name(port, port_name);
>>         >>> + ovsrec_port_set_interfaces(port, &iface, 1);
>>         >>> +
>>         >>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>>         >>> +
>>         >>> + smap_destroy(&options);
>>         >>> +            free(port_name);
>>         >>> +            free(key);
>>         >>> +
>>         >>> +            VLOG_INFO("Creating Mirror in OVS DB");
>>         >>> +            mirror =
>>         ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>>         >>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>>         >>> + ovsrec_mirror_update_output_port_addvalue(mirror, port);
>>         >>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>>         >>> + mirror);
>>         >>> +        }
>>         >>> +
>>         >>> +        struct local_binding *lbinding = local_binding_find(
>>         >>> + pm_ctx->local_bindings,
>>         >>> pb->logical_port);
>>         >>> +        const struct ovsrec_port *p =
>>         >>> + get_port_for_iface(lbinding->iface,
>>         >>> pm_ctx->br_int);
>>         >>> +        if (p) {
>>         >>> +            if
>>         (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>>         >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>         >>> +            } else if
>>         >>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>>         >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>         >>> +            } else {
>>         >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>         >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +    return true;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +check_and_update_mirror_table(const struct sbrec_mirror
>>         *sb_mirror,
>>         >>> +                              struct ovsrec_mirror
>>         *ovs_mirror)
>>         >>> +{
>>         >>> +    char *filter;
>>         >>> +    if ((ovs_mirror->n_select_dst_port)
>>         >>> +            && (ovs_mirror->n_select_src_port)) {
>>         >>> +        filter = "both";
>>         >>> +    } else if (ovs_mirror->n_select_dst_port) {
>>         >>> +        filter = "to-lport";
>>         >>> +    } else {
>>         >>> +        filter = "from-lport";
>>         >>> +    }
>>         >>> +
>>         >>> +    if (strcmp(sb_mirror->filter, filter)) {
>>         >>> +        if (!strcmp(sb_mirror->filter,"from-lport")
>>         >>> + && !strcmp(filter,"both")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_dst_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_dst_port[i]);
>>         >>> +            }
>>         >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>         >>> + && !strcmp(filter,"both")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_src_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_src_port[i]);
>>         >>> +            }
>>         >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>>         >>> + && !strcmp(filter,"from-lport")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_src_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_src_port[i]);
>>         >>> +            }
>>         >>> +        } else if (!strcmp(sb_mirror->filter,"both")
>>         >>> + && !strcmp(filter,"to-lport")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_dst_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_dst_port[i]);
>>         >>> +            }
>>         >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>         >>> + && !strcmp(filter,"from-lport")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_src_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_src_port[i]);
>>         >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_src_port[i]);
>>         >>> +            }
>>         >>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>>         >>> + && !strcmp(filter,"to-lport")) {
>>         >>> +            for (size_t i = 0; i <
>>         ovs_mirror->n_select_dst_port;
>>         >>> i++) {
>>         >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_dst_port[i]);
>>         >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>         >>> + ovs_mirror->select_dst_port[i]);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +check_and_update_interface_table(const struct
>>         sbrec_mirror *sb_mirror,
>>         >>> + struct ovsrec_mirror *ovs_mirror)
>>         >>> +{
>>         >>> +    struct smap options = SMAP_INITIALIZER(&options);
>>         >>> +    char *key, *type;
>>         >>> +    struct ovsrec_interface *iface =
>>         >>> + ovs_mirror->output_port->interfaces[0];
>>         >>> +    struct smap *opts = &iface->options;
>>         >>> +
>>         >>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>>         >>> +    if (erspan_ver) {
>>         >>> +        type = "erspan";
>>         >>> +    } else {
>>         >>> +        type = "gre";
>>         >>> +    }
>>         >>> +    if (strcmp(type, sb_mirror->type)) {
>>         >>> + ovsrec_interface_set_type(iface, sb_mirror->type);
>>         >>> +    }
>>         >>> +
>>         >>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>>         >>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
>>         >>> +    smap_add(&options, "key", key);
>>         >>> +
>>         >>> +    if (!strcmp(sb_mirror->type, "erspan")) {
>>         >>> +        /* Set the ERSPAN index */
>>         >>> +        smap_add(&options, "erspan_idx", key);
>>         >>> +        smap_add(&options, "erspan_ver","1");
>>         >>> +    }
>>         >>> +
>>         >>> + ovsrec_interface_set_options(iface, &options);
>>         >>> +    smap_destroy(&options);
>>         >>> +    free(key);
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +mirror_update(const struct sbrec_mirror *sb_mirror,
>>         >>> +              struct ovsrec_mirror *ovs_mirror)
>>         >>> +{
>>         >>> + check_and_update_interface_table(sb_mirror, ovs_mirror);
>>         >>> +
>>         >>> + check_and_update_mirror_table(sb_mirror, ovs_mirror);
>>         >>> +}
>>         >>> +
>>         >>> +static bool
>>         >>> +mirror_delete(const struct sbrec_port_binding *pb,
>>         >>> +              struct port_mirror_ctx *pm_ctx,
>>         >>> +              struct shash *pb_mirror_map,
>>         >>> +              bool delete_all)
>>         >>> +{
>>         >>> +
>>         >>> +    if (!pm_ctx->ovs_idl_txn) {
>>         >>> +        return false;
>>         >>> +    }
>>         >>> +
>>         >>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>>         >>> +
>>         >>> +    if (!delete_all) {
>>         >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>         >>> + sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
>>         >>> pb->n_mirror_rules) {
>>         >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>         >>> +
>>         >>> +            struct ovsrec_mirror *ovs_mirror = NULL;
>>         >>> +            ovs_mirror =
>>         shash_find_data(pm_ctx->ovs_mirrors,
>>         >>> + pb->mirror_rules[i]->name);
>>         >>> +            if (ovs_mirror) {
>>         >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror->output_port);
>>         >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror);
>>         >>> + ovsrec_port_delete(ovs_mirror->output_port);
>>         >>> + ovsrec_mirror_delete(ovs_mirror);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    struct shash_node *mirror_node;
>>         >>> +    const struct sbrec_port_binding *sb_pb;
>>         >>> +    int attach_cnt = 0;
>>         >>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>>         >>> +        struct ovsrec_mirror *ovs_mirror =
>>         mirror_node->data;
>>         >>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>>         >>> +            /* Find if the mirror has other sources */
>>         >>> +            attach_cnt = 0;
>>         >>> + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>>         >>> + pm_ctx->port_binding_table) {
>>         >>> +                for (size_t i = 0; i <
>>         sb_pb->n_mirror_rules; i++) {
>>         >>> +                    if
>>         (!strcmp(sb_pb->mirror_rules[i]->name,
>>         >>> + ovs_mirror->name)) {
>>         >>> + attach_cnt++;
>>         >>> +                    }
>>         >>> +                }
>>         >>> +            }
>>         >>> +            if (attach_cnt) {
>>         >>> +                /* More than 1 source then just
>>         >>> +                 * update the mirror table
>>         >>> +                 */
>>         >>> +                bool done = false;
>>         >>> +                for (size_t i = 0; ((i <
>>         >>> ovs_mirror->n_select_dst_port)
>>         >>> + && (done ==
>>         >>> false)); i++) {
>>         >>> +                    const struct ovsrec_port *port_rec =
>>         >>> + ovs_mirror->select_dst_port[i];
>>         >>> +                    for (size_t j = 0; j <
>>         port_rec->n_interfaces;
>>         >>> j++) {
>>         >>> +                        const struct ovsrec_interface
>>         *iface_rec;
>>         >>> +
>>         >>> +                        iface_rec = port_rec->interfaces[j];
>>         >>> +                        const char *iface_id =
>>         >>> + smap_get(&iface_rec->external_ids,
>>         >>> + "iface-id");
>>         >>> +                        if
>>         (!strcmp(iface_id,pb->logical_port)) {
>>         >>> + ovsrec_mirror_update_select_dst_port_delvalue(
>>         >>> + ovs_mirror, port_rec);
>>         >>> +                            done = true;
>>         >>> +                            break;
>>         >>> +                        }
>>         >>> +                    }
>>         >>> +                }
>>         >>> +                done = false;
>>         >>> +                for (size_t i = 0; ((i <
>>         >>> ovs_mirror->n_select_src_port)
>>         >>> + && (done ==
>>         >>> false)); i++) {
>>         >>> +                    const struct ovsrec_port *port_rec =
>>         >>> + ovs_mirror->select_src_port[i];
>>         >>> +                    for (size_t j = 0; j <
>>         port_rec->n_interfaces;
>>         >>> j++) {
>>         >>> +                        const struct ovsrec_interface
>>         *iface_rec;
>>         >>> +
>>         >>> +                        iface_rec = port_rec->interfaces[j];
>>         >>> +                        const char *iface_id =
>>         >>> + smap_get(&iface_rec->external_ids,
>>         >>> + "iface-id");
>>         >>> +                        if
>>         (!strcmp(iface_id,pb->logical_port)) {
>>         >>> + ovsrec_mirror_update_select_src_port_delvalue(
>>         >>> + ovs_mirror, port_rec);
>>         >>> +                            done = true;
>>         >>> +                            break;
>>         >>> +                        }
>>         >>> +                    }
>>         >>> +                }
>>         >>> +            } else {
>>         >>> +                /*
>>         >>> +                 * If only 1 source delete the output port
>>         >>> +                 * and then delete the mirror completely
>>         >>> +                 */
>>         >>> +                VLOG_INFO("Only 1 source for the mirror.
>>         Hence
>>         >>> delete it");
>>         >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror->output_port);
>>         >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror);
>>         >>> + ovsrec_port_delete(ovs_mirror->output_port);
>>         >>> + ovsrec_mirror_delete(ovs_mirror);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    const char *used_node, *used_next;
>>         >>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>>         >>> +        sset_delete(&pb_mirrors,
>>         SSET_NODE_FROM_NAME(used_node));
>>         >>> +    }
>>         >>> +    sset_destroy(&pb_mirrors);
>>         >>> +
>>         >>> +    return true;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +find_port_specific_mirrors (const struct
>>         sbrec_port_binding *pb,
>>         >>> +                            struct port_mirror_ctx *pm_ctx,
>>         >>> +                            struct shash *pb_mirror_map)
>>         >>> +{
>>         >>> +    const struct ovsrec_mirror *mirror = NULL;
>>         >>> +
>>         >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror,
>>         pm_ctx->mirror_table) {
>>         >>> +        for (size_t i = 0; i <
>>         mirror->n_select_dst_port; i++) {
>>         >>> +            const struct ovsrec_port *port_rec =
>>         >>> mirror->select_dst_port[i];
>>         >>> +            for (size_t j = 0; j <
>>         port_rec->n_interfaces; j++) {
>>         >>> +                const struct ovsrec_interface *iface_rec;
>>         >>> +                iface_rec = port_rec->interfaces[j];
>>         >>> +                const char *logical_port =
>>         >>> + smap_get(&iface_rec->external_ids, "iface-id");
>>         >>> +                if (!strcmp(logical_port,
>>         pb->logical_port)) {
>>         >>> + shash_add_once(pb_mirror_map, mirror->name,
>>         >>> mirror);
>>         >>> +                }
>>         >>> +            }
>>         >>> +        }
>>         >>> +        for (size_t i = 0; i <
>>         mirror->n_select_src_port; i++) {
>>         >>> +            const struct ovsrec_port *port_rec =
>>         >>> mirror->select_src_port[i];
>>         >>> +            for (size_t j = 0; j <
>>         port_rec->n_interfaces; j++) {
>>         >>> +                const struct ovsrec_interface *iface_rec;
>>         >>> +                iface_rec = port_rec->interfaces[j];
>>         >>> +                const char *logical_port =
>>         >>> + smap_get(&iface_rec->external_ids, "iface-id");
>>         >>> +                if (!strcmp(logical_port,
>>         pb->logical_port)) {
>>         >>> + shash_add_once(pb_mirror_map, mirror->name,
>>         >>> mirror);
>>         >>> +                }
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +void
>>         >>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>         >>> +{
>>         >>> +    ovsdb_idl_add_column(ovs_idl,
>>         &ovsrec_bridge_col_mirrors);
>>         >>> +
>>         >>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>>         >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>>         >>> +    ovsdb_idl_add_column(ovs_idl,
>>         &ovsrec_mirror_col_output_port);
>>         >>> +    ovsdb_idl_add_column(ovs_idl,
>>         &ovsrec_mirror_col_select_dst_port);
>>         >>> +    ovsdb_idl_add_column(ovs_idl,
>>         &ovsrec_mirror_col_select_src_port);
>>         >>> +}
>>         >>> +
>>         >>> +
>>         >>> +void
>>         >>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
>>         >>> +{
>>         >>> +    shash_init(ovs_mirrors);
>>         >>> +}
>>         >>> +
>>         >>> +void
>>         >>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>>         >>> +{
>>         >>> +    const struct sbrec_port_binding *pb;
>>         >>> + SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>>         >>> + pm_ctx->port_binding_table) {
>>         >>> + ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +bool
>>         >>> +ovn_port_mirror_handle_lport(const struct
>>         sbrec_port_binding *pb,
>>         >>> bool removed,
>>         >>> +                     struct port_mirror_ctx *pm_ctx)
>>         >>> +{
>>         >>> +    bool ret = true;
>>         >>> +    struct local_binding *lbinding = local_binding_find(
>>         >>> + pm_ctx->local_bindings,
>>         >>> pb->logical_port);
>>         >>> +
>>         >>> +    if (strcmp(pb->type, "") && (!lbinding)) {
>>         >>> +        return ret;
>>         >>> +    }
>>         >>> +
>>         >>> +    struct shash port_ovs_mirrors =
>>         >>> SHASH_INITIALIZER(&port_ovs_mirrors);
>>         >>> +
>>         >>> +    /* Need to find if mirror needs update */
>>         >>> +    find_port_specific_mirrors(pb, pm_ctx,
>>         &port_ovs_mirrors);
>>         >>> +    if (!removed) {
>>         >>> +        if ((pb->n_mirror_rules == 0)
>>         >>> +              && (shash_is_empty(&port_ovs_mirrors))) {
>>         >>> +            /* No mirror update */
>>         >>> +        } else if (pb->n_mirror_rules ==
>>         >>> shash_count(&port_ovs_mirrors)) {
>>         >>> +            /* Though number of mirror rules are same,
>>         >>> +             * need to verify the contents
>>         >>> +             */
>>         >>> +            for (size_t i = 0; i < pb->n_mirror_rules;
>>         i++) {
>>         >>> +                if (!shash_find(&port_ovs_mirrors,
>>         >>> + pb->mirror_rules[i]->name)) {
>>         >>> +                    /* Mis match in OVN SB DB and OVS DB
>>         >>> +                     * Delete and Create mirror(s) with
>>         proper sources
>>         >>> +                     */
>>         >>> +                    ret = mirror_delete(pb, pm_ctx,
>>         >>> + &port_ovs_mirrors, false);
>>         >>> +                    if (ret) {
>>         >>> +                        ret = mirror_create(pb, pm_ctx);
>>         >>> +                    }
>>         >>> +                    break;
>>         >>> +                }
>>         >>> +            }
>>         >>> +        } else {
>>         >>> +            /* Update Mirror */
>>         >>> +            if (pb->n_mirror_rules >
>>         shash_count(&port_ovs_mirrors)) {
>>         >>> +                /* create mirror,
>>         >>> +                 * if mirror already exists only update
>>         selection
>>         >>> +                 */
>>         >>> +                ret = mirror_create(pb, pm_ctx);
>>         >>> +            } else {
>>         >>> +                /* delete mirror,
>>         >>> +                 * if mirror has other sources only
>>         update selection
>>         >>> +                 */
>>         >>> +                ret = mirror_delete(pb, pm_ctx,
>>         &port_ovs_mirrors,
>>         >>> false);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    } else {
>>         >>> +        ret = mirror_delete(pb, pm_ctx,
>>         &port_ovs_mirrors, true);
>>         >>> +    }
>>         >>> +
>>         >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>         >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>         >>> + &port_ovs_mirrors) {
>>         >>> + shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>>         >>> +    }
>>         >>> + shash_destroy(&port_ovs_mirrors);
>>         >>> +
>>         >>> +    return ret;
>>         >>> +}
>>         >>> +
>>         >>> +bool
>>         >>> +ovn_port_mirror_handle_update(struct port_mirror_ctx
>>         *pm_ctx)
>>         >>> +{
>>         >>> +    const struct sbrec_mirror *mirror = NULL;
>>         >>> +    struct ovsrec_mirror *ovs_mirror = NULL;
>>         >>> +
>>         >>> + SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
>>         >>> pm_ctx->sb_mirror_table) {
>>         >>> +    /* For each tracked mirror entry check if OVS entry
>>         is there*/
>>         >>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>>         >>> mirror->name);
>>         >>> +        if (ovs_mirror) {
>>         >>> +            if (sbrec_mirror_is_deleted(mirror)) {
>>         >>> +                /* Need to delete the mirror in OVS */
>>         >>> +                VLOG_INFO("Delete mirror and remove port");
>>         >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror->output_port);
>>         >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>         >>> + ovs_mirror);
>>         >>> + ovsrec_port_delete(ovs_mirror->output_port);
>>         >>> + ovsrec_mirror_delete(ovs_mirror);
>>         >>> +            } else {
>>         >>> + mirror_update(mirror, ovs_mirror);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    return true;
>>         >>> +}
>>         >>> +
>>         >>> +void
>>         >>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>>         >>> +{
>>         >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>         >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>         >>> + ovs_mirrors) {
>>         >>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
>>         >>> +    }
>>         >>> +    shash_destroy(ovs_mirrors);
>>         >>> +}
>>         >>> diff --git a/controller/mirror.h b/controller/mirror.h
>>         >>> new file mode 100644
>>         >>> index 000000000..85b964f55
>>         >>> --- /dev/null
>>         >>> +++ b/controller/mirror.h
>>         >>> @@ -0,0 +1,53 @@
>>         >>> +/* Copyright (c) 2022 Red Hat, Inc.
>>         >>> + *
>>         >>> + * Licensed under the Apache License, Version 2.0 (the
>>         "License");
>>         >>> + * you may not use this file except in compliance with
>>         the License.
>>         >>> + * You may obtain a copy of the License at:
>>         >>> + *
>>         >>> + * http://www.apache.org/licenses/LICENSE-2.0
>>         >>> + *
>>         >>> + * Unless required by applicable law or agreed to in
>>         writing, software
>>         >>> + * distributed under the License is distributed on an
>>         "AS IS" BASIS,
>>         >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>>         express or
>>         >>> implied.
>>         >>> + * See the License for the specific language governing
>>         permissions and
>>         >>> + * limitations under the License.
>>         >>> + */
>>         >>> +
>>         >>> +#ifndef OVN_MIRROR_H
>>         >>> +#define OVN_MIRROR_H 1
>>         >>> +
>>         >>> +struct ovsdb_idl_txn;
>>         >>> +struct ovsrec_port_table;
>>         >>> +struct ovsrec_bridge;
>>         >>> +struct ovsrec_bridge_table;
>>         >>> +struct ovsrec_open_vswitch_table;
>>         >>> +struct sbrec_chassis;
>>         >>> +struct ovsrec_interface_table;
>>         >>> +struct ovsrec_mirror_table;
>>         >>> +struct sbrec_mirror_table;
>>         >>> +struct sbrec_port_binding_table;
>>         >>> +
>>         >>> +struct port_mirror_ctx {
>>         >>> +    struct shash *ovs_mirrors;
>>         >>> +    struct ovsdb_idl_txn *ovs_idl_txn;
>>         >>> +    const struct ovsrec_port_table *port_table;
>>         >>> +    const struct ovsrec_bridge *br_int;
>>         >>> +    const struct sbrec_chassis *chassis_rec;
>>         >>> +    const struct ovsrec_bridge_table *bridge_table;
>>         >>> +    const struct ovsrec_open_vswitch_table *ovs_table;
>>         >>> +    const struct ovsrec_interface_table *iface_table;
>>         >>> +    const struct ovsrec_mirror_table *mirror_table;
>>         >>> +    const struct sbrec_mirror_table *sb_mirror_table;
>>         >>> +    const struct sbrec_port_binding_table
>>         *port_binding_table;
>>         >>> +    struct shash *local_bindings;
>>         >>> +};
>>         >>> +
>>         >>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
>>         >>> +void ovn_port_mirror_init(struct shash *);
>>         >>> +void ovn_port_mirror_destroy(struct shash *);
>>         >>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>>         >>> +bool ovn_port_mirror_handle_lport(const struct
>>         sbrec_port_binding *pb,
>>         >>> + bool removed,
>>         >>> + struct port_mirror_ctx *pm_ctx);
>>         >>> +bool ovn_port_mirror_handle_update(struct
>>         port_mirror_ctx *pm_ctx);
>>         >>> +#endif
>>         >>> diff --git a/controller/ovn-controller.c
>>         b/controller/ovn-controller.c
>>         >>> index 8895c7a2b..15ab17c4a 100644
>>         >>> --- a/controller/ovn-controller.c
>>         >>> +++ b/controller/ovn-controller.c
>>         >>> @@ -78,6 +78,7 @@
>>         >>>   #include "lib/inc-proc-eng.h"
>>         >>>   #include "lib/ovn-l7.h"
>>         >>>   #include "hmapx.h"
>>         >>> +#include "mirror.h"
>>         >>>
>>         >>>   VLOG_DEFINE_THIS_MODULE(main);
>>         >>>
>>         >>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct
>>         ovsdb_idl *ovs_idl)
>>         >>> ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>>         >>> ovsdb_idl_track_add_column(ovs_idl,
>>         &ovsrec_port_col_interfaces);
>>         >>> ovsdb_idl_track_add_column(ovs_idl,
>>         >>> &ovsrec_port_col_external_ids);
>>         >>> + mirror_register_ovs_idl(ovs_idl);
>>         >>>   }
>>         >>>
>>         >>>   #define SB_NODES \
>>         >>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct
>>         ovsdb_idl *ovs_idl)
>>         >>>       SB_NODE(load_balancer, "load_balancer") \
>>         >>>       SB_NODE(fdb, "fdb") \
>>         >>>       SB_NODE(meter, "meter") \
>>         >>> +    SB_NODE(mirror, "mirror") \
>>         >>>       SB_NODE(static_mac_binding, "static_mac_binding")
>>         >>>
>>         >>>   enum sb_engine_node {
>>         >>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>>         >>>       OVS_NODE(bridge, "bridge") \
>>         >>>       OVS_NODE(port, "port") \
>>         >>>       OVS_NODE(interface, "interface") \
>>         >>> -    OVS_NODE(qos, "qos")
>>         >>> +    OVS_NODE(qos, "qos") \
>>         >>> +    OVS_NODE(mirror, "mirror")
>>         >>>
>>         >>>   enum ovs_engine_node {
>>         >>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>>         >>> @@ -2383,6 +2387,203 @@
>>         load_balancers_by_dp_cleanup(struct hmap *lbs)
>>         >>>       free(lbs);
>>         >>>   }
>>         >>>
>>         >>> +/* Mirror Engine */
>>         >>> +struct ed_type_port_mirror {
>>         >>> +    struct shash ovs_mirrors;
>>         >>> +};
>>         >>> +
>>         >>> +static void *
>>         >>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>>         >>> +                    struct engine_arg *arg OVS_UNUSED)
>>         >>> +{
>>         >>> +    struct ed_type_port_mirror *port_mirror =
>>         xzalloc(sizeof
>>         >>> *port_mirror);
>>         >>> + ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>>         >>> +    return port_mirror;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +en_port_mirror_cleanup(void *data)
>>         >>> +{
>>         >>> +    struct ed_type_port_mirror *port_mirror = data;
>>         >>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +init_port_mirror_ctx(struct engine_node *node,
>>         >>> +                 struct ed_type_runtime_data *rt_data,
>>         >>> +                 struct ed_type_port_mirror
>>         *port_mirror_data,
>>         >>> +                 struct port_mirror_ctx *pm_ctx)
>>         >>> +{
>>         >>> +    struct ovsrec_open_vswitch_table *ovs_table =
>>         >>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("OVS_open_vswitch", node));
>>         >>> +    struct ovsrec_bridge_table *bridge_table =
>>         >>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("OVS_bridge", node));
>>         >>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>>         >>> +    const struct ovsrec_bridge *br_int =
>>         get_br_int(bridge_table,
>>         >>> ovs_table);
>>         >>> +
>>         >>> +    ovs_assert(br_int && chassis_id);
>>         >>> +    const struct sbrec_chassis *chassis = NULL;
>>         >>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>>         >>> +        engine_ovsdb_node_get_index(
>>         >>> + engine_get_input("SB_chassis", node),
>>         >>> +                "name");
>>         >>> +
>>         >>> +    if (chassis_id) {
>>         >>> +        chassis =
>>         chassis_lookup_by_name(sbrec_chassis_by_name,
>>         >>> chassis_id);
>>         >>> +    }
>>         >>> +    ovs_assert(chassis);
>>         >>> +
>>         >>> +    struct ovsrec_port_table *port_table =
>>         >>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("OVS_port", node));
>>         >>> +
>>         >>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
>>         >>> + engine_get_input_data("ovs_interface_shadow", node);
>>         >>> +
>>         >>> +    struct ovsrec_mirror_table *mirror_table =
>>         >>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("OVS_mirror", node));
>>         >>> +
>>         >>> +    struct sbrec_port_binding_table *pb_table =
>>         >>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("SB_port_binding", node));
>>         >>> +
>>         >>> +    struct sbrec_mirror_table *sb_mirror_table =
>>         >>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>>         >>> + engine_get_input("SB_mirror", node));
>>         >>> +
>>         >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>         >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>         >>> + &port_mirror_data->ovs_mirrors) {
>>         >>> + shash_delete(&port_mirror_data->ovs_mirrors,
>>         ovs_mirror_node);
>>         >>> +    }
>>         >>> +
>>         >>> +    const struct ovsrec_mirror *ovsmirror = NULL;
>>         >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>>         >>> + shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
>>         >>> ovsmirror);
>>         >>> +    }
>>         >>> +
>>         >>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>>         >>> +    pm_ctx->port_table = port_table;
>>         >>> +    pm_ctx->iface_table = iface_shadow->iface_table;
>>         >>> +    pm_ctx->mirror_table = mirror_table;
>>         >>> +    pm_ctx->port_binding_table = pb_table;
>>         >>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
>>         >>> +    pm_ctx->br_int = br_int;
>>         >>> +    pm_ctx->chassis_rec = chassis;
>>         >>> +    pm_ctx->bridge_table = bridge_table;
>>         >>> +    pm_ctx->ovs_table = ovs_table;
>>         >>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>>         >>> +    pm_ctx->local_bindings =
>>         &rt_data->lbinding_data.bindings;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +en_port_mirror_run(struct engine_node *node, void *data)
>>         >>> +{
>>         >>> +    struct port_mirror_ctx pm_ctx;
>>         >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>         >>> +    struct ed_type_runtime_data *rt_data =
>>         >>> + engine_get_input_data("runtime_data", node);
>>         >>> +
>>         >>> +    init_port_mirror_ctx(node, rt_data,
>>         port_mirror_data, &pm_ctx);
>>         >>> +
>>         >>> + ovn_port_mirror_run(&pm_ctx);
>>         >>> +    engine_set_node_state(node, EN_UPDATED);
>>         >>> +}
>>         >>> +
>>         >>> +static bool
>>         >>> +port_mirror_runtime_data_handler(struct engine_node
>>         *node, void *data)
>>         >>> +{
>>         >>> +    struct ed_type_runtime_data *rt_data =
>>         >>> + engine_get_input_data("runtime_data", node);
>>         >>> +
>>         >>> +    /* There is no tracked data. Fall back to full
>>         recompute of
>>         >>> port_mirror */
>>         >>> +    if (!rt_data->tracked) {
>>         >>> +        return false;
>>         >>> +    }
>>         >>> +
>>         >>> +    struct hmap *tracked_dp_bindings =
>>         &rt_data->tracked_dp_bindings;
>>         >>> +    if (hmap_is_empty(tracked_dp_bindings)) {
>>         >>> +        return true;
>>         >>> +    }
>>         >>> +
>>         >>> +    struct port_mirror_ctx pm_ctx;
>>         >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>         >>> +    init_port_mirror_ctx(node, rt_data,
>>         port_mirror_data, &pm_ctx);
>>         >>> +
>>         >>> +    struct tracked_datapath *tdp;
>>         >>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>>         >>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>>         >>> +            /* Fall back to full recompute when a local
>>         datapath
>>         >>> +             * is added or deleted. */
>>         >>> +            return false;
>>         >>> +        }
>>         >>> +
>>         >>> +        struct shash_node *shash_node;
>>         >>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>>         >>> +            struct tracked_lport *lport = shash_node->data;
>>         >>> +            bool removed =
>>         >>> + lport->tracked_type == TRACKED_RESOURCE_REMOVED ?
>>         >>> true: false;
>>         >>> +            if (!ovn_port_mirror_handle_lport(lport->pb,
>>         removed,
>>         >>> &pm_ctx)) {
>>         >>> +                return false;
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    engine_set_node_state(node, EN_UPDATED);
>>         >>> +    return true;
>>         >>> +}
>>         >>> +
>>         >>> +static bool
>>         >>> +port_mirror_port_binding_handler(struct engine_node
>>         *node, void *data)
>>         >>> +{
>>         >>> +    struct port_mirror_ctx pm_ctx;
>>         >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>         >>> +    struct ed_type_runtime_data *rt_data =
>>         >>> + engine_get_input_data("runtime_data", node);
>>         >>> +
>>         >>> +    init_port_mirror_ctx(node, rt_data,
>>         port_mirror_data, &pm_ctx);
>>         >>> +
>>         >>> +    /* handle port binding updates (i.,e when the mirror
>>         column
>>         >>> +     * of port_binding is updated)
>>         >>> +     */
>>         >>> +    const struct sbrec_port_binding *pb;
>>         >>> + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>>         >>> + pm_ctx.port_binding_table) {
>>         >>> +        bool removed = sbrec_port_binding_is_deleted(pb);
>>         >>> +        if (!ovn_port_mirror_handle_lport(pb, removed,
>>         &pm_ctx)) {
>>         >>> +            return false;
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    engine_set_node_state(node, EN_UPDATED);
>>         >>> +    return true;
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>> +static bool
>>         >>> +port_mirror_sb_mirror_handler(struct engine_node *node,
>>         void *data)
>>         >>> +{
>>         >>> +    struct port_mirror_ctx pm_ctx;
>>         >>> +    struct ed_type_port_mirror *port_mirror_data = data;
>>         >>> +    struct ed_type_runtime_data *rt_data =
>>         >>> + engine_get_input_data("runtime_data", node);
>>         >>> +
>>         >>> +    init_port_mirror_ctx(node, rt_data,
>>         port_mirror_data, &pm_ctx);
>>         >>> +
>>         >>> +    /* handle sb mirror updates
>>         >>> +     */
>>         >>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>>         >>> +        return false;
>>         >>> +    }
>>         >>> +
>>         >>> +    engine_set_node_state(node, EN_UPDATED);
>>         >>> +    return true;
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>>   /* Engine node which is used to handle the Non VIF data
>>         like
>>         >>>    *   - OVS patch ports
>>         >>>    *   - Tunnel ports and the related chassis information.
>>         >>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>>         >>> ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups,
>>         "port_groups");
>>         >>>       ENGINE_NODE(northd_options, "northd_options");
>>         >>>       ENGINE_NODE(dhcp_options, "dhcp_options");
>>         >>> +    ENGINE_NODE(port_mirror, "port_mirror");
>>         >>>
>>         >>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME,
>>         NAME_STR);
>>         >>>       SB_NODES
>>         >>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>>         >>> engine_add_input(&en_flow_output, &en_pflow_output,
>>         >>> flow_output_pflow_output_handler);
>>         >>>
>>         >>> + engine_add_input(&en_port_mirror, &en_ovs_open_vswitch,
>>         NULL);
>>         >>> + engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>>         >>> + engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>>         >>> + engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>>         >>> + engine_add_input(&en_port_mirror, &en_ovs_port,
>>         >>> engine_noop_handler);
>>         >>> + engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>>         >>> + engine_noop_handler);
>>         >>> + engine_add_input(&en_flow_output, &en_port_mirror,
>>         >>> + engine_noop_handler);
>>         >>> + engine_add_input(&en_port_mirror, &en_runtime_data,
>>         >>> + port_mirror_runtime_data_handler);
>>         >>> + engine_add_input(&en_port_mirror, &en_sb_mirror,
>>         >>> + port_mirror_sb_mirror_handler);
>>         >>> + engine_add_input(&en_port_mirror, &en_sb_port_binding,
>>         >>> + port_mirror_port_binding_handler);
>>         >>> +
>>         >>>       struct engine_arg engine_arg = {
>>         >>>           .sb_idl = ovnsb_idl_loop.idl,
>>         >>>           .ovs_idl = ovs_idl_loop.idl,
>>         >>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>>         >>>
>>         >>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>>         >>> time_msec());
>>         >>> -                    if (ovnsb_idl_txn) {
>>         >>> -                        if (ofctrl_has_backlog()) {
>>         >>> -                            /* When there are in-flight
>>         messages
>>         >>> pending to
>>         >>> -                             * ovs-vswitchd, we should
>>         hold on
>>         >>> recomputing so
>>         >>> -                             * that the previous flow
>>         installations
>>         >>> won't be
>>         >>> -                             * delayed.  However, we
>>         still want to
>>         >>> try if
>>         >>> -                             * recompute is not needed
>>         and we can
>>         >>> quickly
>>         >>> -                             * incrementally process the
>>         new
>>         >>> changes, to avoid
>>         >>> -                             * unnecessarily forced
>>         recomputes
>>         >>> later on.  This
>>         >>> -                             * is because the OVSDB
>>         change tracker
>>         >>> cannot
>>         >>> -                             * preserve tracked changes
>>         across
>>         >>> iterations.  If
>>         >>> -                             * change tracking is
>>         improved, we can
>>         >>> simply skip
>>         >>> -                             * this round of engine_run and
>>         >>> continue processing
>>         >>> -                             * acculated changes
>>         incrementally
>>         >>> later when
>>         >>> -                             * ofctrl_has_backlog()
>>         returns false. */
>>         >>> - engine_run(false);
>>         >>> -                        } else {
>>         >>> - engine_run(true);
>>         >>> -                        }
>>         >>> -                    } else {
>>         >>> -                        /* Even if there's no SB DB
>>         transaction
>>         >>> available,
>>         >>> +
>>         >>> +                    bool allow_engine_recompute = true;
>>         >>> +
>>         >>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>>         >>> + ofctrl_has_backlog()) {
>>         >>> +                        /* When there are in-flight
>>         messages
>>         >>> pending to
>>         >>> +                         * ovs-vswitchd, we should hold on
>>         >>> recomputing so
>>         >>> +                         * that the previous flow
>>         installations
>>         >>> won't be
>>         >>> +                         * delayed. However, we still
>>         want to try if
>>         >>> +                         * recompute is not needed and
>>         we can quickly
>>         >>> +                         * incrementally process the new
>>         changes,
>>         >>> to avoid
>>         >>> +                         * unnecessarily forced
>>         recomputes later
>>         >>> on.  This
>>         >>> +                         * is because the OVSDB change
>>         tracker cannot
>>         >>> +                         * preserve tracked changes across
>>         >>> iterations.  If
>>         >>> +                         * change tracking is improved,
>>         we can
>>         >>> simply skip
>>         >>> +                         * this round of engine_run and
>>         continue
>>         >>> processing
>>         >>> +                         * acculated changes
>>         incrementally later when
>>         >>> +                         * ofctrl_has_backlog() returns
>>         false. */
>>         >>> +
>>         >>> +                        /* Even if there's no SB/OVS DB
>>         transaction
>>         >>> available,
>>         >>>                            * try to run the engine so
>>         that we can
>>         >>> handle any
>>         >>>                            * incremental changes that
>>         don't require
>>         >>> a recompute.
>>         >>>                            * If a recompute is required,
>>         the engine
>>         >>> will abort,
>>         >>>                            * triggerring a full run in
>>         the next
>>         >>> iteration.
>>         >>>                            */
>>         >>> - engine_run(false);
>>         >>> + allow_engine_recompute = false;
>>         >>>                       }
>>         >>> +
>>         >>> + engine_run(allow_engine_recompute);
>>         >>> +
>>         >>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>>         >>> time_msec());
>>         >>>                       if (engine_has_updated()) {
>>         >>> diff --git a/northd/en-northd.c b/northd/en-northd.c
>>         >>> index 7fe83db64..608220b1f 100644
>>         >>> --- a/northd/en-northd.c
>>         >>> +++ b/northd/en-northd.c
>>         >>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node
>>         *node, void
>>         >>> *data)
>>         >>> EN_OVSDB_GET(engine_get_input("NB_acl", node));
>>         >>> input_data.nbrec_static_mac_binding_table =
>>         >>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding",
>>         node));
>>         >>> +    input_data.nbrec_mirror_table =
>>         >>> + EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>>         >>>
>>         >>> input_data.sbrec_sb_global_table =
>>         >>> EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>>         >>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node
>>         *node,
>>         >>> void *data)
>>         >>> EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>>         >>> input_data.sbrec_static_mac_binding_table =
>>         >>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding",
>>         node));
>>         >>> +    input_data.sbrec_mirror_table =
>>         >>> + EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>>         >>>
>>         >>>       northd_run(&input_data, data,
>>         >>> eng_ctx->ovnnb_idl_txn,
>>         >>> diff --git a/northd/inc-proc-northd.c
>>         b/northd/inc-proc-northd.c
>>         >>> index 54e0ad3b0..ac27a730e 100644
>>         >>> --- a/northd/inc-proc-northd.c
>>         >>> +++ b/northd/inc-proc-northd.c
>>         >>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>         >>>       NB_NODE(acl, "acl") \
>>         >>>       NB_NODE(logical_router, "logical_router") \
>>         >>>       NB_NODE(qos, "qos") \
>>         >>> +    NB_NODE(mirror, "mirror") \
>>         >>>       NB_NODE(meter, "meter") \
>>         >>>       NB_NODE(meter_band, "meter_band") \
>>         >>>       NB_NODE(logical_router_port, "logical_router_port") \
>>         >>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>         >>>       SB_NODE(logical_flow, "logical_flow") \
>>         >>>       SB_NODE(logical_dp_group, "logical_DP_group") \
>>         >>>       SB_NODE(multicast_group, "multicast_group") \
>>         >>> +    SB_NODE(mirror, "mirror") \
>>         >>>       SB_NODE(meter, "meter") \
>>         >>>       SB_NODE(meter_band, "meter_band") \
>>         >>>       SB_NODE(datapath_binding, "datapath_binding") \
>>         >>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct
>>         ovsdb_idl_loop
>>         >>> *nb,
>>         >>> engine_add_input(&en_northd, &en_nb_acl, NULL);
>>         >>> engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>>         >>> engine_add_input(&en_northd, &en_nb_qos, NULL);
>>         >>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>>         >>> engine_add_input(&en_northd, &en_nb_meter, NULL);
>>         >>> engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>>         >>> engine_add_input(&en_northd, &en_nb_logical_router_port,
>>         NULL);
>>         >>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct
>>         ovsdb_idl_loop
>>         >>> *nb,
>>         >>> engine_add_input(&en_northd, &en_sb_address_set, NULL);
>>         >>> engine_add_input(&en_northd, &en_sb_port_group, NULL);
>>         >>> engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>>         >>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>>         >>> engine_add_input(&en_northd, &en_sb_meter, NULL);
>>         >>> engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>>         >>> engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>>         >>> diff --git a/northd/northd.c b/northd/northd.c
>>         >>> index b7388afc5..52abdda28 100644
>>         >>> --- a/northd/northd.c
>>         >>> +++ b/northd/northd.c
>>         >>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>>         >>>       free(requested_chassis_sb);
>>         >>>   }
>>         >>>
>>         >>> +static void
>>         >>> +do_sb_mirror_addition(struct northd_input *input_data,
>>         >>> +                      const struct ovn_port *op)
>>         >>> +{
>>         >>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>         >>> +        const struct sbrec_mirror *sb_mirror;
>>         >>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>>         >>> + input_data->sbrec_mirror_table) {
>>         >>> +            if (!strcmp(sb_mirror->name,
>>         >>> + op->nbsp->mirror_rules[i]->name)) {
>>         >>> +                /* Add the value to SB */
>>         >>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>>         >>> + sb_mirror);
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +sbrec_port_binding_update_mirror_rules(struct northd_input
>>         >>> *input_data,
>>         >>> +                                       const struct
>>         ovn_port *op)
>>         >>> +{
>>         >>> +    size_t i = 0;
>>         >>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>>         >>> +        /* Needs deletion in SB */
>>         >>> +        struct shash nb_mirror_rules =
>>         >>> SHASH_INITIALIZER(&nb_mirror_rules);
>>         >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>         >>> + shash_add(&nb_mirror_rules,
>>         >>> + op->nbsp->mirror_rules[i]->name,
>>         >>> + op->nbsp->mirror_rules[i]);
>>         >>> +        }
>>         >>> +
>>         >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>         >>> +            if (!shash_find(&nb_mirror_rules,
>>         >>> + op->sb->mirror_rules[i]->name)) {
>>         >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>         >>> + op->sb->mirror_rules[i]);
>>         >>> +            }
>>         >>> +        }
>>         >>> +
>>         >>> +        struct shash_node *node, *next;
>>         >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>         >>> + shash_delete(&nb_mirror_rules, node);
>>         >>> +        }
>>         >>> + shash_destroy(&nb_mirror_rules);
>>         >>> +
>>         >>> +    } else if (op->sb->n_mirror_rules <
>>         op->nbsp->n_mirror_rules) {
>>         >>> +        /* Needs addition in SB */
>>         >>> + do_sb_mirror_addition(input_data, op);
>>         >>> +    } else if (op->sb->n_mirror_rules ==
>>         op->nbsp->n_mirror_rules) {
>>         >>> +        /*
>>         >>> +         * Check if its the same mirrors on both SB and
>>         NB DBs
>>         >>> +         * If not update accordingly.
>>         >>> +         */
>>         >>> +        bool needs_sb_addition = false;
>>         >>> +        struct shash nb_mirror_rules =
>>         >>> SHASH_INITIALIZER(&nb_mirror_rules);
>>         >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>         >>> + shash_add(&nb_mirror_rules,
>>         >>> + op->nbsp->mirror_rules[i]->name,
>>         >>> + op->nbsp->mirror_rules[i]);
>>         >>> +        }
>>         >>> +
>>         >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>         >>> +            if (!shash_find(&nb_mirror_rules,
>>         >>> + op->sb->mirror_rules[i]->name)) {
>>         >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>         >>> + op->sb->mirror_rules[i]);
>>         >>> + needs_sb_addition = true;
>>         >>> +            }
>>         >>> +        }
>>         >>> +
>>         >>> +        struct shash_node *node, *next;
>>         >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>         >>> + shash_delete(&nb_mirror_rules, node);
>>         >>> +        }
>>         >>> + shash_destroy(&nb_mirror_rules);
>>         >>> +
>>         >>> +        if (needs_sb_addition) {
>>         >>> + do_sb_mirror_addition(input_data, op);
>>         >>> +        }
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>>   static void
>>         >>>   ovn_port_update_sbrec(struct northd_input *input_data,
>>         >>>                         struct ovsdb_idl_txn *ovnsb_txn,
>>         >>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct
>>         northd_input
>>         >>> *input_data,
>>         >>>           }
>>         >>> sbrec_port_binding_set_external_ids(op->sb, &ids);
>>         >>>           smap_destroy(&ids);
>>         >>> +
>>         >>> +        if (!op->nbsp->n_mirror_rules) {
>>         >>> +            /* Nothing is set. Clear mirror_rules from
>>         pb. */
>>         >>> + sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>>         >>> +        } else {
>>         >>> +            /* Check if SB DB update needed */
>>         >>> + sbrec_port_binding_update_mirror_rules(input_data, op);
>>         >>> +        }
>>         >>> +
>>         >>>       }
>>         >>>       if (op->tunnel_key != op->sb->tunnel_key) {
>>         >>> sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>>         >>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input
>>         *input_data,
>>         >>>       shash_destroy(&sb_meters);
>>         >>>   }
>>         >>>
>>         >>> +static bool
>>         >>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>>         >>> +                  const struct sbrec_mirror *sb_mirror)
>>         >>> +{
>>         >>> +
>>         >>> +    if (nb_mirror->index != sb_mirror->index) {
>>         >>> +        return true;
>>         >>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>>         >>> +        return true;
>>         >>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>>         >>> +        return true;
>>         >>> +    } else if (strcmp(nb_mirror->filter,
>>         sb_mirror->filter)) {
>>         >>> +        return true;
>>         >>> +    }
>>         >>> +
>>         >>> +    return false;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn
>>         *ovnsb_txn,
>>         >>> +                             const char *mirror_name,
>>         >>> +                             const struct nbrec_mirror
>>         *nb_mirror,
>>         >>> +                             struct shash *sb_mirrors,
>>         >>> +                             struct sset *used_sb_mirrors)
>>         >>> +{
>>         >>> +    const struct sbrec_mirror *sb_mirror;
>>         >>> +    bool new_sb_mirror = false;
>>         >>> +
>>         >>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>>         >>> +    if (!sb_mirror) {
>>         >>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>>         >>> + sbrec_mirror_set_name(sb_mirror, mirror_name);
>>         >>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>>         >>> +        new_sb_mirror = true;
>>         >>> +    }
>>         >>> +    sset_add(used_sb_mirrors, mirror_name);
>>         >>> +
>>         >>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror,
>>         >>> sb_mirror)) {
>>         >>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>>         >>> + sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>>         >>> + sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>>         >>> + sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +sync_mirrors(struct northd_input *input_data,
>>         >>> +            struct ovsdb_idl_txn *ovnsb_txn)
>>         >>> +{
>>         >>> +    struct shash sb_mirrors =
>>         SHASH_INITIALIZER(&sb_mirrors);
>>         >>> +    struct sset used_sb_mirrors =
>>         SSET_INITIALIZER(&used_sb_mirrors);
>>         >>> +
>>         >>> +    const struct sbrec_mirror *sb_mirror;
>>         >>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>>         >>> input_data->sbrec_mirror_table) {
>>         >>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>>         >>> +    }
>>         >>> +
>>         >>> +    const struct nbrec_mirror *nb_mirror;
>>         >>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
>>         >>> input_data->nbrec_mirror_table) {
>>         >>> + sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
>>         >>> nb_mirror,
>>         >>> + &sb_mirrors, &used_sb_mirrors);
>>         >>> +    }
>>         >>> +
>>         >>> +    const char *used_mirror;
>>         >>> +    const char *used_mirror_next;
>>         >>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
>>         >>> &used_sb_mirrors) {
>>         >>> + shash_find_and_delete(&sb_mirrors, used_mirror);
>>         >>> + sset_delete(&used_sb_mirrors,
>>         >>> SSET_NODE_FROM_NAME(used_mirror));
>>         >>> +    }
>>         >>> + sset_destroy(&used_sb_mirrors);
>>         >>> +
>>         >>> +    struct shash_node *node, *next;
>>         >>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>>         >>> + sbrec_mirror_delete(node->data);
>>         >>> + shash_delete(&sb_mirrors, node);
>>         >>> +    }
>>         >>> +    shash_destroy(&sb_mirrors);
>>         >>> +}
>>         >>> +
>>         >>>   /*
>>         >>>    * struct 'dns_info' is used to sync the DNS records
>>         between OVN
>>         >>> Northbound db
>>         >>>    * and Southbound db.
>>         >>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input
>>         *input_data,
>>         >>>       sync_address_sets(input_data, ovnsb_txn,
>>         &data->datapaths);
>>         >>>       sync_port_groups(input_data, ovnsb_txn,
>>         &data->port_groups);
>>         >>>       sync_meters(input_data, ovnsb_txn,
>>         &data->meter_groups);
>>         >>> +    sync_mirrors(input_data, ovnsb_txn);
>>         >>>       sync_dns_entries(input_data, ovnsb_txn,
>>         &data->datapaths);
>>         >>> cleanup_stale_fdb_entries(input_data, &data->datapaths);
>>         >>> stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>>         >>> diff --git a/northd/northd.h b/northd/northd.h
>>         >>> index da90e2815..17a62ea10 100644
>>         >>> --- a/northd/northd.h
>>         >>> +++ b/northd/northd.h
>>         >>> @@ -36,6 +36,7 @@ struct northd_input {
>>         >>>       const struct nbrec_acl_table *nbrec_acl_table;
>>         >>>       const struct nbrec_static_mac_binding_table
>>         >>> *nbrec_static_mac_binding_table;
>>         >>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>>         >>>
>>         >>>       /* Southbound table references */
>>         >>>       const struct sbrec_sb_global_table
>>         *sbrec_sb_global_table;
>>         >>> @@ -55,6 +56,7 @@ struct northd_input {
>>         >>>       const struct sbrec_chassis_private_table
>>         >>> *sbrec_chassis_private_table;
>>         >>>       const struct sbrec_static_mac_binding_table
>>         >>> *sbrec_static_mac_binding_table;
>>         >>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>>         >>>
>>         >>>       /* Indexes */
>>         >>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
>>         >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>>         >>> index 174364c8b..01de55222 100644
>>         >>> --- a/ovn-nb.ovsschema
>>         >>> +++ b/ovn-nb.ovsschema
>>         >>> @@ -1,7 +1,7 @@
>>         >>>   {
>>         >>>       "name": "OVN_Northbound",
>>         >>> -    "version": "6.3.0",
>>         >>> -    "cksum": "4042813038 31869",
>>         >>> +    "version": "6.4.0",
>>         >>> +    "cksum": "589874483 33352",
>>         >>>       "tables": {
>>         >>>           "NB_Global": {
>>         >>>               "columns": {
>>         >>> @@ -132,6 +132,11 @@
>>         >>> "refType": "weak"},
>>         >>> "min": 0,
>>         >>> "max": 1}},
>>         >>> +                "mirror_rules": {"type": {"key":
>>         {"type": "uuid",
>>         >>> + "refTable": "Mirror",
>>         >>> + "refType": "weak"},
>>         >>> + "min": 0,
>>         >>> + "max": "unlimited"}},
>>         >>>                   "ha_chassis_group": {
>>         >>>                       "type": {"key": {"type": "uuid",
>>         >>>                                        "refTable":
>>         "HA_Chassis_Group",
>>         >>> @@ -301,6 +306,28 @@
>>         >>>                       "type": {"key": "string", "value":
>>         "string",
>>         >>>                                "min": 0, "max":
>>         "unlimited"}}},
>>         >>>               "isRoot": false},
>>         >>> +        "Mirror": {
>>         >>> +            "columns": {
>>         >>> +                "name": {"type": "string"},
>>         >>> +                "filter": {"type": {"key": {"type":
>>         "string",
>>         >>> + "enum": ["set",
>>         >>> ["from-lport",
>>         >>> + "to-lport",
>>         >>> + "both"]]}}},
>>         >>> +                "sink":{"type": "string"},
>>         >>> +                "type": {"type": {"key": {"type": "string",
>>         >>> + "enum": ["set", ["gre",
>>         >>> + "erspan"]]}}},
>>         >>> +                "index": {"type": "integer"},
>>         >>> +                "src": {"type": {"key": {"type": "uuid",
>>         >>> + "refTable":
>>         >>> "Logical_Switch_Port",
>>         >>> + "refType": "weak"},
>>         >>> + "min": 0,
>>         >>> + "max": "unlimited"}},
>>         >>> +                "external_ids": {
>>         >>> +                    "type": {"key": "string", "value":
>>         "string",
>>         >>> +                             "min": 0, "max":
>>         "unlimited"}}},
>>         >>> +            "indexes": [["name"]],
>>         >>> +            "isRoot": true},
>>         >>>           "Meter": {
>>         >>>               "columns": {
>>         >>>                   "name": {"type": "string"},
>>         >>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>>         >>> index f41e9d7c0..d8730c8fc 100644
>>         >>> --- a/ovn-nb.xml
>>         >>> +++ b/ovn-nb.xml
>>         >>> @@ -1554,6 +1554,11 @@
>>         >>>         </column>
>>         >>>       </group>
>>         >>>
>>         >>> +    <column name="mirror_rules">
>>         >>> +        Mirror rules that apply to logical switch port
>>         which is the
>>         >>> source.
>>         >>> +        Please see the <ref table="Mirror"/> table.
>>         >>> +    </column>
>>         >>> +
>>         >>>       <column name="ha_chassis_group">
>>         >>>         References a row in the OVN Northbound database's
>>         >>>         <ref table="HA_Chassis_Group"
>>         db="OVN_Northbound"/> table.
>>         >>> @@ -2491,6 +2496,64 @@
>>         >>>       </column>
>>         >>>     </table>
>>         >>>
>>         >>> +  <table name="Mirror" title="Mirror Entry">
>>         >>> +    <p>
>>         >>> +      Each row in this table represents one Mirror that
>>         can be used
>>         >>> for
>>         >>> +      port mirroring. These Mirrors are referenced by the
>>         >>> +      <ref column="mirror_rules"
>>         table="Logical_Switch_Port"/>
>>         >>> column in
>>         >>> +      the <ref table="Logical_Switch_Port"/> table.
>>         >>> +    </p>
>>         >>> +
>>         >>> +    <column name="name">
>>         >>> +      <p>
>>         >>> +        Represents the name of the mirror.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="filter">
>>         >>> +      <p>
>>         >>> +        The value of this field represents selection
>>         criteria of
>>         >>> the mirror.
>>         >>> +        Supported values for filter to-lport /
>>         from-lport / both
>>         >>> +        to-lport - to mirror packets coming into logical
>>         port
>>         >>> +        from-lport - to mirror packets going out of
>>         logical port
>>         >>> +        both - to mirror packets coming into and going
>>         out of
>>         >>> logical port.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="sink">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the
>>         destination/sink of
>>         >>> the mirror.
>>         >>> +        The value it takes is an IP address of the sink
>>         port.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="type">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the type of
>>         the tunnel
>>         >>> used for
>>         >>> +        sending the mirrored packets. Supported Tunnel
>>         types gre
>>         >>> and erspan
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="index">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the tunnel
>>         ID. Depending
>>         >>> on the
>>         >>> +        tunnel type configured, GRE key value if type
>>         GRE and
>>         >>> erspan_idx value
>>         >>> +        if ERSPAN
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="src">
>>         >>> +      <p>
>>         >>> +        The value of this field represents a list of
>>         source ports
>>         >>> for the
>>         >>> +        mirror. Please see the <ref
>>         table="Logical_Switch_Port"/>
>>         >>> table.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="external_ids">
>>         >>> +      See <em>External IDs</em> at the beginning of this
>>         document.
>>         >>> +    </column>
>>         >>> +  </table>
>>         >>> +
>>         >>>     <table name="Meter" title="Meter entry">
>>         >>>       <p>
>>         >>>         Each row in this table represents a meter that
>>         can be used
>>         >>> for QoS or
>>         >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>>         >>> index 576ebbdeb..b83134416 100644
>>         >>> --- a/ovn-sb.ovsschema
>>         >>> +++ b/ovn-sb.ovsschema
>>         >>> @@ -1,7 +1,7 @@
>>         >>>   {
>>         >>>       "name": "OVN_Southbound",
>>         >>> -    "version": "20.25.0",
>>         >>> -    "cksum": "53184112 28845",
>>         >>> +    "version": "20.26.0",
>>         >>> +    "cksum": "2344151793 30004",
>>         >>>       "tables": {
>>         >>>           "SB_Global": {
>>         >>>               "columns": {
>>         >>> @@ -142,6 +142,23 @@
>>         >>>               "indexes": [["datapath", "tunnel_key"],
>>         >>> ["datapath", "name"]],
>>         >>>               "isRoot": true},
>>         >>> +        "Mirror": {
>>         >>> +            "columns": {
>>         >>> +                "name": {"type": "string"},
>>         >>> +                "filter": {"type": {"key": {"type":
>>         "string",
>>         >>> + "enum": ["set",
>>         >>> + ["from-lport",
>>         >>> + "to-lport","both"]]}}},
>>         >>> +                "sink":{"type": "string"},
>>         >>> +                "type": {"type": {"key": {"type": "string",
>>         >>> + "enum": ["set",
>>         >>> + ["gre",
>>         >>> "erspan"]]}}},
>>         >>> +                "index": {"type": "integer"},
>>         >>> +                "external_ids": {
>>         >>> +                    "type": {"key": "string", "value":
>>         "string",
>>         >>> +                             "min": 0, "max":
>>         "unlimited"}}},
>>         >>> +            "indexes": [["name"]],
>>         >>> +            "isRoot": true},
>>         >>>           "Meter": {
>>         >>>               "columns": {
>>         >>>                   "name": {"type": "string"},
>>         >>> @@ -230,6 +247,11 @@
>>         >>> "refTable": "Encap",
>>         >>> "refType": "weak"},
>>         >>> "min": 0, "max": "unlimited"}},
>>         >>> +                "mirror_rules": {"type": {"key":
>>         {"type": "uuid",
>>         >>> + "refTable": "Mirror",
>>         >>> + "refType": "weak"},
>>         >>> + "min": 0,
>>         >>> + "max": "unlimited"}},
>>         >>>                   "mac": {"type": {"key": "string",
>>         >>> "min": 0,
>>         >>> "max": "unlimited"}},
>>         >>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>>         >>> index 315d60853..05c0db6b4 100644
>>         >>> --- a/ovn-sb.xml
>>         >>> +++ b/ovn-sb.xml
>>         >>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>>         >>>       </column>
>>         >>>     </table>
>>         >>>
>>         >>> +  <table name="Mirror" title="Mirror Entry">
>>         >>> +    <p>
>>         >>> +      Each row in this table represents one Mirror that
>>         can be used
>>         >>> for
>>         >>> +      port mirroring. These Mirrors are referenced by the
>>         >>> +      <ref column="mirror_rules" table="Port_Binding"/>
>>         column in
>>         >>> +      the <ref table="Port_Binding"/> table.
>>         >>> +    </p>
>>         >>> +
>>         >>> +    <column name="name">
>>         >>> +      <p>
>>         >>> +        Represents the name of the mirror.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="filter">
>>         >>> +      <p>
>>         >>> +        The value of this field represents selection
>>         criteria of
>>         >>> the mirror.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="sink">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the
>>         destination/sink of
>>         >>> the mirror.
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="type">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the type of
>>         the tunnel
>>         >>> used for
>>         >>> +        sending the mirrored packets
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="index">
>>         >>> +      <p>
>>         >>> +        The value of this field represents the key/idx
>>         depending on
>>         >>> the
>>         >>> +        tunnel type configured
>>         >>> +      </p>
>>         >>> +    </column>
>>         >>> +
>>         >>> +    <column name="external_ids">
>>         >>> +      See <em>External IDs</em> at the beginning of this
>>         document.
>>         >>> +    </column>
>>         >>> +  </table>
>>         >>> +
>>         >>>     <table name="Meter" title="Meter entry">
>>         >>>       <p>
>>         >>>         Each row in this table represents a meter that
>>         can be used
>>         >>> for QoS or
>>         >>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>>         >>>         </column>
>>         >>>       </group>
>>         >>>
>>         >>> +    <column name="mirror_rules">
>>         >>> +        Mirror rules that apply to the port binding.
>>         >>> +        Please see the <ref table="Mirror"/> table.
>>         >>> +    </column>
>>         >>> +
>>         >>>       <group title="Patch Options">
>>         >>>         <p>
>>         >>>           These options apply to logical ports with <ref
>>         >>> column="type"/> of
>>         >>> diff --git a/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>>         b/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>>         >>> index 4d480e357..d79f9d929 100644
>>         >>> --- a/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>>         >>> +++ b/tests/ovn-nbctl.at <http://ovn-nbctl.at>
>>         >>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list],
>>         [0], [dnl
>>         >>>
>>         >>>   dnl
>>         >>>
>>         ---------------------------------------------------------------------
>>         >>>
>>         >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>>         >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
>>         10.10.10.1])
>>         >>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both
>>         10.10.10.2])
>>         >>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport
>>         10.10.10.3])
>>         >>> +AT_CHECK([ovn-nbctl ls-add sw0])
>>         >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>>         >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>>         >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>>         >>> +
>>         >>> +dnl Add duplicate mirror name
>>         >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
>>         >>> 10.10.10.5], [1], [], [stderr])
>>         >>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>>         >>> +
>>         >>> +dnl Attach invalid source port to mirror
>>         >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4
>>         mirror3], [1], [],
>>         >>> [stderr])
>>         >>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>>         >>> +
>>         >>> +dnl Attach source port to invalid mirror
>>         >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3
>>         mirror4], [1], [],
>>         >>> [stderr])
>>         >>> +AT_CHECK([grep 'mirror name not found' stderr], [0],
>>         [ignore])
>>         >>> +
>>         >>> +dnl Attach source port to mirror
>>         >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>>         >>> +
>>         >>> +dnl Attach one more source port to mirror
>>         >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>>         >>> +
>>         >>> +dnl Verify if multiple ports are attached to the same
>>         mirror properly
>>         >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>         >>> +mirror1:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  10.10.10.1
>>         >>> +  Filter   :  from-lport
>>         >>> +  Index/Key:  0
>>         >>> +  Sources  :  None attached
>>         >>> +mirror2:
>>         >>> +  Type     :  erspan
>>         >>> +  Sink     :  10.10.10.2
>>         >>> +  Filter   :  both
>>         >>> +  Index/Key:  1
>>         >>> +  Sources  :  None attached
>>         >>> +mirror3:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  10.10.10.3
>>         >>> +  Filter   :  to-lport
>>         >>> +  Index/Key:  2
>>         >>> +  Sources  :  sw0-port1  sw0-port3
>>         >>> +])
>>         >>> +
>>         >>> +dnl Detach one source port from mirror
>>         >>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>>         >>> +
>>         >>> +dnl Verify if detach source port from mirror happens
>>         properly
>>         >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>         >>> +mirror1:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  10.10.10.1
>>         >>> +  Filter   :  from-lport
>>         >>> +  Index/Key:  0
>>         >>> +  Sources  :  None attached
>>         >>> +mirror2:
>>         >>> +  Type     :  erspan
>>         >>> +  Sink     :  10.10.10.2
>>         >>> +  Filter   :  both
>>         >>> +  Index/Key:  1
>>         >>> +  Sources  :  None attached
>>         >>> +mirror3:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  10.10.10.3
>>         >>> +  Filter   :  to-lport
>>         >>> +  Index/Key:  2
>>         >>> +  Sources  :  sw0-port1
>>         >>> +])
>>         >>> +
>>         >>> +dnl Delete a single mirror which has source attached.
>>         >>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
>>         >>> +
>>         >>> +dnl Check if the detach happened from source properly
>>         >>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1
>>         mirror_rules
>>         >>> |  cut -b 3], [0], [dnl
>>         >>> +
>>         >>> +])
>>         >>> +
>>         >>> +dnl Check if the mirror deleted properly
>>         >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>         >>> +mirror1:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  10.10.10.1
>>         >>> +  Filter   :  from-lport
>>         >>> +  Index/Key:  0
>>         >>> +  Sources  :  None attached
>>         >>> +mirror2:
>>         >>> +  Type     :  erspan
>>         >>> +  Sink     :  10.10.10.2
>>         >>> +  Filter   :  both
>>         >>> +  Index/Key:  1
>>         >>> +  Sources  :  None attached
>>         >>> +])
>>         >>> +
>>         >>> +dnl Delete another mirror
>>         >>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
>>         >>> +
>>         >>> +dnl Update the Sink address
>>         >>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>>         >>> +
>>         >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>         >>> +mirror1:
>>         >>> +  Type     :  gre
>>         >>> +  Sink     :  192.168.1.13
>>         >>> +  Filter   :  from-lport
>>         >>> +  Index/Key:  0
>>         >>> +  Sources  :  None attached
>>         >>> +])
>>         >>> +
>>         >>> +dnl Delete all mirrors
>>         >>> +AT_CHECK([ovn-nbctl mirror-del])
>>         >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>         >>> +])])
>>         >>> +
>>         >>> +dnl
>>         >>>
>>         ---------------------------------------------------------------------
>>         >>> +
>>         >>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>>         >>>   AT_CHECK([ovn-nbctl lr-add lr0])
>>         >>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2
>>         192.168.1.2],
>>         >>> [1], [],
>>         >>> diff --git a/tests/ovn-northd.at <http://ovn-northd.at>
>>         b/tests/ovn-northd.at <http://ovn-northd.at>
>>         >>> index 4f399eccb..4e6c268e4 100644
>>         >>> --- a/tests/ovn-northd.at <http://ovn-northd.at>
>>         >>> +++ b/tests/ovn-northd.at <http://ovn-northd.at>
>>         >>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT
>>         meter_me__${acl1}
>>         >>> meter_me__${acl2}
>>         >>>   AT_CLEANUP
>>         >>>   ])
>>         >>>
>>         >>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>         >>> +AT_SETUP([Check NB-SB mirrors sync])
>>         >>> +AT_KEYWORDS([mirrors])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0
>>         both 10.10.10.2
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>         >>> +"10.10.10.2"
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>         >>> +erspan
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>         >>> +0
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>         >>> +both
>>         >>> +])
>>         >>> +
>>         >>> +check ovn-nbctl --wait=sb \
>>         >>> +    -- set mirror . sink=192.168.1.13
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>         >>> +"192.168.1.13"
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>         >>> +erspan
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>         >>> +0
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>         >>> +both
>>         >>> +])
>>         >>> +
>>         >>> +check ovn-nbctl --wait=sb \
>>         >>> +    -- set mirror . type=gre
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>         >>> +gre
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>         >>> +"192.168.1.13"
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>         >>> +0
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>         >>> +both
>>         >>> +])
>>         >>> +
>>         >>> +check ovn-nbctl --wait=sb \
>>         >>> +    -- set mirror . index=12
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>         >>> +12
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>         >>> +gre
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>         >>> +"192.168.1.13"
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>         >>> +both
>>         >>> +])
>>         >>> +
>>         >>> +check ovn-nbctl --wait=sb \
>>         >>> +    -- set mirror . filter=to-lport
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>         >>> +to-lport
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>         >>> +12
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>         >>> +gre
>>         >>> +])
>>         >>> +
>>         >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>         >>> +"192.168.1.13"
>>         >>> +])
>>         >>> +
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>>   OVN_FOR_EACH_NORTHD_NO_HV([
>>         >>>   AT_SETUP([ACL skip hints for stateless config])
>>         >>>   AT_KEYWORDS([acl])
>>         >>> diff --git a/tests/ovn.at <http://ovn.at> b/tests/ovn.at
>>         <http://ovn.at>
>>         >>> index f8b8db4df..cd5527ea1 100644
>>         >>> --- a/tests/ovn.at <http://ovn.at>
>>         >>> +++ b/tests/ovn.at <http://ovn.at>
>>         >>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>>         >>>   AT_CLEANUP
>>         >>>   ])
>>         >>>
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror])
>>         >>> +AT_KEYWORDS([Mirror])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>>         >>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>>         >>> + options:rxq_pcap=hv1/vif1-rx.pcap \
>>         >>> +    ofport-request=1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>>         >>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>>         >>> + options:rxq_pcap=hv1/vif2-rx.pcap \
>>         >>> +    ofport-request=1
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +ovn-nbctl dump-flows > sbflows
>>         >>> +AT_CAPTURE_FILE([sbflows])
>>         >>> +
>>         >>> +for i in 1 2; do
>>         >>> +    : > vif$i.expected
>>         >>> +done
>>         >>> +
>>         >>> +net_add n2
>>         >>> +
>>         >>> +sim_add hv2
>>         >>> +as hv2
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:02:02:00\"
>>         >>> +ovn_attach n2 br-phys 192.168.1.12
>>         >>> +
>>         >>> +OVN_POPULATE_ARP
>>         >>> +
>>         >>> +as hv1
>>         >>> +
>>         >>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC
>>         IPV4_DST
>>         >>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM]
>>         ENCAP_TYPE FILTER
>>         >>> +#
>>         >>> +# Causes a packet to be received on INPORT.  The packet
>>         is an ICMPv4
>>         >>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST,
>>         IP_CHSUM and
>>         >>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and
>>         EXP_ICMP_CHKSUM are
>>         >>> +# provided, then it should be the ip and icmp checksums
>>         of the packet
>>         >>> +# responded; otherwise, no reply is expected.
>>         >>> +# In the absence of an ip checksum calculation helpers,
>>         this relies
>>         >>> +# on the caller to provide the checksums for the ip and
>>         icmp headers.
>>         >>> +# XXX This should be more systematic.
>>         >>> +#
>>         >>> +# INPORT is an lport number, e.g. 11 for vif11.
>>         >>> +# ETH_SRC and ETH_DST are each 12 hex digits.
>>         >>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>>         >>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>>         >>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>>         >>> +# ENCAP_TYPE - gre or erspan
>>         >>> +# FILTER - Mirror Filter - to-lport / from-lport
>>         >>> +test_ipv4_icmp_request() {
>>         >>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4
>>         ipv4_dst=$5
>>         >>> ip_chksum=$6 icmp_chksum=$7
>>         >>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9
>>         >>> mirror_encap_type=${10} mirror_filter=${11}
>>         >>> +    shift; shift; shift; shift; shift; shift; shift
>>         >>> +    shift; shift; shift; shift;
>>         >>> +
>>         >>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>>         >>> +    local ip_ttl=02
>>         >>> +    local icmp_id=5fbf
>>         >>> +    local icmp_seq=0001
>>         >>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>>         >>> +    local icmp_type_code_request=0800
>>         >>> +    local
>>         >>>
>>         icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>         >>> +    local
>>         >>>
>>         packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>>         >>> +
>>         >>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport
>>         $packet
>>         >>> +
>>         >>> +    # Expect to receive the reply, if any. In same port
>>         where
>>         >>> packet was sent.
>>         >>> +    # Note: src and dst fields are expected to be reversed.
>>         >>> +    local icmp_type_code_response=0000
>>         >>> +    local reply_icmp_ttl=fe
>>         >>> +    local
>>         >>>
>>         reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>         >>> +    local
>>         >>>
>>         reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>>         >>> +    echo $reply >> vif$inport.expected
>>         >>> +    local remote_mac=000000020200
>>         >>> +    local local_mac=000000010200
>>         >>> +    if test ${mirror_encap_type} = "gre" ; then
>>         >>> +        local
>>         >>>
>>         ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>>         >>> +        if test ${mirror_filter} = "to-lport" ; then
>>         >>> +            local
>>         >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>>         >>> +        elif test ${mirror_filter} = "from-lport" ; then
>>         >>> +            local
>>         >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>>         >>> +        fi
>>         >>> +    elif test ${mirror_encap_type} = "erspan" ; then
>>         >>> +        local
>>         ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>>         >>> +        local erspan_seq0=100088be000000001000000000000000
>>         >>> +        local erspan_seq1=100088be000000011000000000000000
>>         >>> +        if test ${mirror_filter} = "to-lport" ; then
>>         >>> +            local
>>         >>>
>>         mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
>>
>>         >>>
>>         >>> +        elif test ${mirror_filter} = "from-lport" ; then
>>         >>> +            local
>>         >>>
>>         mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>>         >>> +        fi
>>         >>> +    fi
>>         >>> +    echo $mirror >> br-phys_n1.expected
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>> +# Set IPs
>>         >>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>>         >>> +l1_ip=$(ip_to_hex 192 168 1 2)
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +
>>         >>> +# Send ping packet and check for mirrored packet of the
>>         reply
>>         >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>>         >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>>         >>> +
>>         >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>>         >>> [br-phys_n1.expected])
>>         >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>         >>> +
>>         >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>         >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>         >>> +rm -f br-phys_n1.expected
>>         >>> +rm -f vif1.expected
>>         >>> +
>>         >>> +check ovn-nbctl set mirror . type=erspan
>>         >>> +
>>         >>> +# Send ping packet and check for mirrored packet of the
>>         reply
>>         >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>>         >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>>         >>> +
>>         >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>>         >>> [br-phys_n1.expected])
>>         >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>         >>> +
>>         >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>         >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>         >>> +rm -f br-phys_n1.expected
>>         >>> +rm -f vif1.expected
>>         >>> +
>>         >>> +check ovn-nbctl set mirror . filter=from-lport
>>         >>> +
>>         >>> +# Send ping packet and check for mirrored packet of the
>>         request
>>         >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>>         >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>>         >>> +
>>         >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>>         >>> [br-phys_n1.expected])
>>         >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>         >>> +
>>         >>> +as hv1 reset_pcap_file vif1 hv1/vif1
>>         >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>         >>> +rm -f br-phys_n1.expected
>>         >>> +rm -f vif1.expected
>>         >>> +
>>         >>> +check ovn-nbctl set mirror . type=gre
>>         >>> +
>>         >>> +# Send ping packet and check for mirrored packet of the
>>         request
>>         >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
>>         >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>>         >>> +
>>         >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
>>         >>> [br-phys_n1.expected])
>>         >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>         >>> +
>>         >>> +echo "---------OVN NB Mirror-----"
>>         >>> +ovn-nbctl mirror-list
>>         >>> +
>>         >>> +echo "---------OVS Mirror----"
>>         >>> +ovs-vsctl list Mirror
>>         >>> +
>>         >>> +echo "-----------------------"
>>         >>> +
>>         >>> +echo "Verifying Mirror deletion in OVS"
>>         >>> +# Set vif1 iface-id such that OVN releases port binding
>>         >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>>         >>> +])
>>         >>> +
>>         >>> +# Set vif1 iface-id back to ls1-lp1
>>         >>> +check ovs-vsctl set interface vif1
>>         external_ids:iface-id=ls1-lp1
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror
>>         mirror0 name) =
>>         >>> "mirror0"])
>>         >>> +
>>         >>> +# Delete vif1 so that OVN releases port binding
>>         >>> +check ovs-vsctl del-port br-int vif1
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror
>>         | wc -l)])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1], [hv2])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror test bulk swap attachments])
>>         >>> +AT_KEYWORDS([Mirror test bulk swap attachments])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls1-lp2 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>         >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp2 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>         >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif3 -- \
>>         >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif4 -- \
>>         >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +# Equal detaches and attaches
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>         >>> +
>>         >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror test bulk attach multiple])
>>         >>> +AT_KEYWORDS([Mirror test bulk attach multiple])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls1-lp2 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>         >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp2 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>         >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif3 -- \
>>         >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif4 -- \
>>         >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +check ovn-nbctl mirror-del
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +
>>         >>> +# Attaches multiple mirrors
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>>         >>> +
>>         >>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror test bulk more detach and less attach])
>>         >>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls1-lp2 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>         >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp2 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>         >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif3 -- \
>>         >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif4 -- \
>>         >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +check ovn-nbctl mirror-del
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +# Detaches more than attaches
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
>>         >>> +
>>         >>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror test bulk attach more than detach])
>>         >>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls1-lp2 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>         >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp2 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>         >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif3 -- \
>>         >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif4 -- \
>>         >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +# Attaches more than detaches
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +as hv1
>>         >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>         >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>         >>> +
>>         >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>         >>> +
>>         >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>> +
>>         >>> +OVN_FOR_EACH_NORTHD([
>>         >>> +AT_SETUP([Mirror test bulk detach multiple])
>>         >>> +AT_KEYWORDS([Mirror test bulk detach multiple])
>>         >>> +ovn_start
>>         >>> +
>>         >>> +# Logical network:
>>         >>> +# One LR - R1 has switch ls1 (191.168.1.0/24
>>         <http://191.168.1.0/24>) connected to it,
>>         >>> +# and has switch ls2 (172.16.1.0/24
>>         <http://172.16.1.0/24>) connected to it.
>>         >>> +
>>         >>> +ovn-nbctl lr-add R1
>>         >>> +
>>         >>> +ovn-nbctl ls-add ls1
>>         >>> +ovn-nbctl ls-add ls2
>>         >>> +
>>         >>> +# Connect ls1 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1
>>         192.168.1.1/24 <http://192.168.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port
>>         rp-ls1 \
>>         >>> +    type=router options:router-port=ls1
>>         >>> addresses=\"00:00:00:01:02:f1\"
>>         >>> +
>>         >>> +# Connect ls2 to R1
>>         >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
>>         <http://172.16.1.1/24>
>>         >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port
>>         rp-ls2 \
>>         >>> +    type=router options:router-port=ls2
>>         >>> addresses=\"00:00:00:01:02:f2\"
>>         >>> +
>>         >>> +# Create logical port ls1-lp1 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>         >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>         >>> +
>>         >>> +# Create logical port ls1-lp2 in ls1
>>         >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>         >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>         >>> +
>>         >>> +# Create logical port ls2-lp1 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>         >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>         >>> +
>>         >>> +# Create logical port ls2-lp2 in ls2
>>         >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>         >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>         >>> +
>>         >>> +ovn-nbctl lsp-add ls1 ln-public
>>         >>> +ovn-nbctl lsp-set-type ln-public localnet
>>         >>> +ovn-nbctl lsp-set-addresses ln-public unknown
>>         >>> +ovn-nbctl lsp-set-options ln-public network_name=public
>>         >>> +
>>         >>> +# Create one hypervisor and create OVS ports
>>         corresponding to
>>         >>> logical ports.
>>         >>> +net_add n1
>>         >>> +
>>         >>> +sim_add hv1
>>         >>> +as hv1
>>         >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
>>         >>> other-config:hwaddr=\"00:00:00:01:02:00\"
>>         >>> +ovn_attach n1 br-phys 192.168.1.11
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif1 -- \
>>         >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif2 -- \
>>         >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif3 -- \
>>         >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
>>         >>> +
>>         >>> +ovs-vsctl -- add-port br-int vif4 -- \
>>         >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
>>         >>> +
>>         >>> +ovs-vsctl set open .
>>         external-ids:ovn-bridge-mappings=public:br-phys
>>         >>> +
>>         >>> +# Allow some time for ovn-northd and ovn-controller to
>>         catch up.
>>         >>> +wait_for_ports_up
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>         >>> +
>>         >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport
>>         192.168.1.12
>>         >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>         >>> +
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +# Detaches all
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>         >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>         >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>         >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>         >>> +check ovn-nbctl --wait=hv sync
>>         >>> +
>>         >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror
>>         | wc -l)])
>>         >>> +
>>         >>> +OVN_CLEANUP([hv1])
>>         >>> +AT_CLEANUP
>>         >>> +])
>>         >>>
>>         >>>   OVN_FOR_EACH_NORTHD([
>>         >>>   AT_SETUP([Port Groups])
>>         >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>>         >>> index 811468dc6..af2e61435 100644
>>         >>> --- a/utilities/ovn-nbctl.c
>>         >>> +++ b/utilities/ovn-nbctl.c
>>         >>> @@ -271,6 +271,19 @@ QoS commands:\n\
>>         >>>                               remove QoS rules from SWITCH\n\
>>         >>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>>         >>>   \n\
>>         >>> +Mirror commands:\n\
>>         >>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
>>         >>> +                            add a mirror with given name\n\
>>         >>> +                            specify TYPE 'gre' or
>>         'erspan'\n\
>>         >>> +                            specify the tunnel INDEX
>>         value\n\
>>         >>> + (indicates key if GRE\n\
>>         >>> + erpsan_idx if ERSPAN)\n\
>>         >>> +                            specify FILTER for mirroring
>>         selection\n\
>>         >>> + 'to-lport' / 'from-lport' / 'both'\n\
>>         >>> +                            specify Sink / Destination
>>         i.e. Remote
>>         >>> IP\n\
>>         >>> +  mirror-del [NAME]         remove mirrors\n\
>>         >>> +  mirror-list               print mirrors\n\
>>         >>> +\n\
>>         >>>   Meter commands:\n\
>>         >>>     [--fair]\n\
>>         >>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
>>         >>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>>         >>>                               set dhcpv6 options for PORT\n\
>>         >>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options
>>         for PORT\n\
>>         >>>     lsp-get-ls PORT           get the logical switch
>>         which the port
>>         >>> belongs to\n\
>>         >>> +  lsp-attach-mirror PORT MIRROR attach source PORT to
>>         MIRROR\n\
>>         >>> +  lsp-detach-mirror PORT MIRROR detach source PORT from
>>         MIRROR\n\
>>         >>>   \n\
>>         >>>   Forwarding group commands:\n\
>>         >>>     [--liveness]\n\
>>         >>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct
>>         ctl_context *ctx)
>>         >>> ovsdb_idl_add_column(ctx->idl,
>>         >>> &nbrec_logical_switch_port_col_type);
>>         >>>   }
>>         >>>
>>         >>> +static void
>>         >>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>>         >>> +{
>>         >>> + ovsdb_idl_add_column(ctx->idl,
>>         >>> &nbrec_logical_switch_port_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl,
>>         >>> + &nbrec_logical_switch_port_col_mirror_rules);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>         >>> +}
>>         >>> +
>>         >>> +static int
>>         >>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
>>         >>> +{
>>         >>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>>         >>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>>         >>> +
>>         >>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
>>         >>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
>>         >>> +
>>         >>> +    return strcmp(mirror1->name,mirror2->name);
>>         >>> +}
>>         >>> +
>>         >>> +static char * OVS_WARN_UNUSED_RESULT
>>         >>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const
>>         char *id,
>>         >>> +                    bool must_exist,
>>         >>> +                    const struct nbrec_mirror **mirror_p)
>>         >>> +{
>>         >>> +    const struct nbrec_mirror *mirror = NULL;
>>         >>> +    *mirror_p = NULL;
>>         >>> +
>>         >>> +    struct uuid mirror_uuid;
>>         >>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>>         >>> +    if (is_uuid) {
>>         >>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl,
>>         &mirror_uuid);
>>         >>> +    }
>>         >>> +
>>         >>> +    if (!mirror) {
>>         >>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>         >>> +            if (!strcmp(mirror->name, id)) {
>>         >>> +                break;
>>         >>> +            }
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    if (!mirror && must_exist) {
>>         >>> +        return xasprintf("%s: mirror %s not found",
>>         >>> +                         id, is_uuid ? "UUID" : "name");
>>         >>> +    }
>>         >>> +
>>         >>> +    *mirror_p = mirror;
>>         >>> +    return NULL;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>>         >>> +{
>>         >>> +    const char *port = ctx->argv[1];
>>         >>> +    const char *mirror_name = ctx->argv[2];
>>         >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>>         >>> +    const struct nbrec_mirror *mirror;
>>         >>> +
>>         >>> +    char *error;
>>         >>> +
>>         >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +
>>         >>> +    /*check if a mirror rule actually exists on that
>>         name or not*/
>>         >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name,
>>         true, &mirror);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +    /* Check if same mirror rule already exists for the
>>         lsp */
>>         >>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>>         >>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>>         >>> +            bool may_exist = shash_find(&ctx->options,
>>         >>> "--may-exist") != NULL;
>>         >>> +            if (!may_exist) {
>>         >>> +                ctl_error(ctx, "Same mirror already
>>         existed on the
>>         >>> lsp %s.",
>>         >>> + ctx->argv[1]);
>>         >>> +                return;
>>         >>> +            }
>>         >>> +            return;
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +
>>         nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp,
>>         mirror);
>>         >>> + nbrec_mirror_update_src_addvalue(mirror,lsp);
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>>         >>> +{
>>         >>> +    const char *port = ctx->argv[1];
>>         >>> +    const char *mirror_name = ctx->argv[2];
>>         >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
>>         >>> +    const struct nbrec_mirror *mirror;
>>         >>> +
>>         >>> +    char *error;
>>         >>> +
>>         >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +
>>         >>> +    /*check if a mirror rule actually exists on that
>>         name or not*/
>>         >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name,
>>         true, &mirror);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +
>>         nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp,
>>         mirror);
>>         >>> + nbrec_mirror_update_src_delvalue(mirror,lsp);
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>>   static void
>>         >>>   nbctl_lsp_set_type(struct ctl_context *ctx)
>>         >>>   {
>>         >>> @@ -7241,6 +7380,211 @@
>>         cmd_ha_ch_grp_set_chassis_prio(struct
>>         >>> ctl_context *ctx)
>>         >>> nbrec_ha_chassis_set_priority(ha_chassis, priority);
>>         >>>   }
>>         >>>
>>         >>> +static char * OVS_WARN_UNUSED_RESULT
>>         >>> +parse_filter(const char *arg, const char **selection_p)
>>         >>> +{
>>         >>> +    /* Validate selection.  Only require the first
>>         letter. */
>>         >>> +    if (arg[0] == 't') {
>>         >>> +        *selection_p = "to-lport";
>>         >>> +    } else if (arg[0] == 'f') {
>>         >>> +        *selection_p = "from-lport";
>>         >>> +    } else if (arg[0] == 'b') {
>>         >>> +        *selection_p = "both";
>>         >>> +    } else {
>>         >>> +        *selection_p = NULL;
>>         >>> +        return xasprintf("%s: selection must be
>>         \"to-lport\" or "
>>         >>> + "\"from-lport\" or \"both\" ", arg);
>>         >>> +    }
>>         >>> +    return NULL;
>>         >>> +}
>>         >>> +
>>         >>> +static char * OVS_WARN_UNUSED_RESULT
>>         >>> +parse_type(const char *arg, const char **type_p)
>>         >>> +{
>>         >>> +    /* Validate type.  Only require the first letter. */
>>         >>> +    if (arg[0] == 'g') {
>>         >>> +        *type_p = "gre";
>>         >>> +    } else if (arg[0] == 'e') {
>>         >>> +        *type_p = "erspan";
>>         >>> +    } else {
>>         >>> +        *type_p = NULL;
>>         >>> +        return xasprintf("%s: type must be \"gre\" or "
>>         >>> + "\"erspan\"", arg);
>>         >>> +    }
>>         >>> +    return NULL;
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
>>         >>> +{
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_mirror_add(struct ctl_context *ctx)
>>         >>> +{
>>         >>> +    const char *filter = NULL;
>>         >>> +    const char *sink_ip = NULL;
>>         >>> +    const char *type = NULL;
>>         >>> +    const char *name = NULL;
>>         >>> +    char *new_sink_ip = NULL;
>>         >>> +    int64_t index;
>>         >>> +    char *error = NULL;
>>         >>> +    const struct nbrec_mirror *mirror_check = NULL;
>>         >>> +
>>         >>> +    /* Mirror Name */
>>         >>> +    name = ctx->argv[1];
>>         >>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>>         >>> +        if (!strcmp(mirror_check->name, name)) {
>>         >>> +            ctl_error(ctx, "Mirror with %s name already
>>         exists.",
>>         >>> +                      name);
>>         >>> +            return;
>>         >>> +        }
>>         >>> +    }
>>         >>> +
>>         >>> +    /* Tunnel Type - GRE/ERSPAN */
>>         >>> +    error = parse_type(ctx->argv[2], &type);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +    /* tunnel index / GRE key / ERSPAN idx */
>>         >>> +    index = atoi(ctx->argv[3]);
>>         >> Shouldn't we validate the input is an actual number?
>>         >>
>>         >>> +
>>         >>> +    /* Filter for mirroring */
>>         >>> +    error = parse_filter(ctx->argv[4], &filter);
>>         >>> +    if (error) {
>>         >>> +        ctx->error = error;
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +    /* Destination / Sink details */
>>         >>> +    sink_ip = ctx->argv[5];
>>         >>> +
>>         >>> +    /* check if it is a valid ip */
>>         >>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>>         >>> +    if (!new_sink_ip) {
>>         >>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>>         >>> +    }
>>         >>> +
>>         >>> +    if (new_sink_ip) {
>>         >>> +        free(new_sink_ip);
>>         >>> +    } else {
>>         >>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +    /* Create the mirror. */
>>         >>> +    struct nbrec_mirror *mirror =
>>         nbrec_mirror_insert(ctx->txn);
>>         >>> +    nbrec_mirror_set_name(mirror, name);
>>         >>> +    nbrec_mirror_set_index(mirror, index);
>>         >>> +    nbrec_mirror_set_filter(mirror, filter);
>>         >>> +    nbrec_mirror_set_type(mirror, type);
>>         >>> +    nbrec_mirror_set_sink(mirror, sink_ip);
>>         >>> +
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
>>         >>> +{
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_mirror_del(struct ctl_context *ctx)
>>         >>> +{
>>         >>> +    const struct nbrec_mirror *mirror, *next;
>>         >>> +
>>         >>> +    /* If a name is not specified, delete all mirrors. */
>>         >>> +    if (ctx->argc == 1) {
>>         >>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next,
>>         ctx->idl) {
>>         >>> + nbrec_mirror_delete(mirror);
>>         >>> +        }
>>         >>> +        return;
>>         >>> +    }
>>         >>> +
>>         >>> +    /* Remove the matching mirror. */
>>         >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>         >>> +        if (strcmp(ctx->argv[1], mirror->name)) {
>>         >>> +            continue;
>>         >>> +        }
>>         >>> +        nbrec_mirror_delete(mirror);
>>         >>> +        return;
>>         >>> +    }
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
>>         >>> +{
>>         >>> + ovsdb_idl_add_column(ctx->idl,
>>         >>> &nbrec_logical_switch_port_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>         >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>         >>> +}
>>         >>> +
>>         >>> +static void
>>         >>> +nbctl_mirror_list(struct ctl_context *ctx)
>>         >>> +{
>>         >>> +
>>         >>> +    const struct nbrec_mirror **mirrors = NULL;
>>         >>> +    const struct nbrec_mirror *mirror;
>>         >>> +    size_t n_capacity = 0;
>>         >>> +    size_t n_mirrors = 0;
>>         >>> +
>>         >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>         >>> +        if (n_mirrors == n_capacity) {
>>         >>> +            mirrors = x2nrealloc(mirrors, &n_capacity,
>>         sizeof
>>         >>> *mirrors);
>>         >>> +        }
>>         >>> +
>>         >>> +        mirrors[n_mirrors] = mirror;
>>         >>> +        n_mirrors++;
>>         >>> +    }
>>         >>> +
>>         >>> +    if (n_mirrors) {
>>         >>> +        qsort(mirrors, n_mirrors, sizeof *mirrors,
>>         mirror_cmp);
>>         >>> +    }
>>         >>> +
>>         >>> +    for (size_t i = 0; i < n_mirrors; i++) {
>>         >>> +        mirror = mirrors[i];
>>         >>> + ds_put_format(&ctx->output, "%s:\n", mirror->name);
>>         >>> +        /* print all the values */
>>         >>> + ds_put_format(&ctx->output, "  Type     : %s\n",
>>         >>> mirror->type);
>>         >>> + ds_put_format(&ctx->output, "  Sink     : %s\n",
>>         >>> mirror->sink);
>>         >>> + ds_put_format(&ctx->output, "  Filter   : %s\n",
>>         >>> mirror->filter);
>>         >>> + ds_put_format(&ctx->output, "  Index/Key: %ld\n",
>>         >>> + (long int)
>>         >>> mirror->index);
>>         >> You don't ned to cast if you pass %d formatter instead of
>>         %ld. The
>>         >> same applies to other places in the patch where you cast
>>         to long int.
>>         >> In general, casting is not needed and should be avoided.
>>         >>
>>         >>> + ds_put_cstr(&ctx->output, "  Sources  :");
>>         >>> +        if (mirror->n_src > 0) {
>>         >>> +            struct svec srcs;
>>         >>> +            const char *src;
>>         >>> +            size_t j;
>>         >>> +            svec_init(&srcs);
>>         >>> +            for (j = 0; j < mirror->n_src; j++) {
>>         >>> +                svec_add(&srcs, mirror->src[j]->name);
>>         >>> +            }
>>         >>> +            svec_sort(&srcs);
>>         >>> +            SVEC_FOR_EACH (j, src, &srcs) {
>>         >>> + ds_put_format(&ctx->output, "  %s", src);
>>         >>> +            }
>>         >>> +            svec_destroy(&srcs);
>>         >>> +        } else {
>>         >>> + ds_put_cstr(&ctx->output, "  None attached");
>>         >>> +        }
>>         >>> + ds_put_cstr(&ctx->output, "\n");
>>         >>> +    }
>>         >>> +
>>         >>> +    free(mirrors);
>>         >>> +}
>>         >>> +
>>         >>>   static const struct ctl_table_class
>>         tables[NBREC_N_TABLES] = {
>>         >>> [NBREC_TABLE_DHCP_OPTIONS].row_ids
>>         >>>       = {{&nbrec_logical_switch_port_col_name, NULL,
>>         >>> @@ -7334,6 +7678,15 @@ static const struct
>>         ctl_command_syntax
>>         >>> nbctl_commands[] = {
>>         >>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list,
>>         nbctl_qos_list,
>>         >>>         NULL, "", RO },
>>         >>>
>>         >>> +    /* mirror commands. */
>>         >>> +    { "mirror-add", 5, 5,
>>         >>> +      "NAME TYPE INDEX FILTER IP",
>>         >>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL,
>>         "--may-exist",
>>         >>> RW },
>>         >>> +    { "mirror-del", 0, 1, "[NAME]",
>>         >>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "",
>>         RW },
>>         >>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list,
>>         >>> nbctl_mirror_list,
>>         >>> +      NULL, "", RO },
>>         >>> +
>>         >>>       /* meter commands. */
>>         >>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
>>         >>> nbctl_pre_meter_add,
>>         >>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>>         >>> @@ -7388,6 +7741,10 @@ static const struct
>>         ctl_command_syntax
>>         >>> nbctl_commands[] = {
>>         >>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>>         >>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
>>         >>> nbctl_lsp_get_ls,
>>         >>>         NULL, "", RO },
>>         >>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR",
>>         nbctl_pre_lsp_mirror,
>>         >>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
>>         >>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR",
>>         nbctl_pre_lsp_mirror,
>>         >>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>>         >>>
>>         >>>       /* forwarding group commands. */
>>         >>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP
>>         VMAC PORT...",
>>         >>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>>         >>> index f60dde1b6..3d73e9e25 100644
>>         >>> --- a/utilities/ovn-sbctl.c
>>         >>> +++ b/utilities/ovn-sbctl.c
>>         >>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>>         >>> ovsdb_idl_add_column(ctx->idl,
>>         &sbrec_port_binding_col_chassis);
>>         >>> ovsdb_idl_add_column(ctx->idl,
>>         &sbrec_port_binding_col_datapath);
>>         >>> ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>>         >>> + ovsdb_idl_add_column(ctx->idl,
>>         >>> &sbrec_port_binding_col_mirror_rules);
>>         >>>
>>         >>> ovsdb_idl_add_column(ctx->idl,
>>         >>> &sbrec_logical_flow_col_logical_datapath);
>>         >>> ovsdb_idl_add_column(ctx->idl,
>>         >>> &sbrec_logical_flow_col_logical_dp_group);
>>         >>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class
>>         >>> tables[SBREC_N_TABLES] = {
>>         >>> [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>>         >>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>>         >>>
>>         >>> +    [SBREC_TABLE_MIRROR].row_ids[0]
>>         >>> +    = {&sbrec_mirror_col_name, NULL, NULL},
>>         >>> +
>>         >>>       [SBREC_TABLE_METER].row_ids[0]
>>         >>>       = {&sbrec_meter_col_name, NULL, NULL},
>>         >>>
>>         >>> --
>>         >>> 2.31.1
>>         >>>
>>
Ihar Hrachyshka Nov. 17, 2022, 1:06 a.m. UTC | #8
The code is now closer to final, so I've drilled down and listed a 
number of nits and style changes recommended. These are not complete and 
you should apply similar changes to the whole body of the patch.

I reviewed all the code except test suite changes since you were going 
to reshuffle the tests, esp. bulk tests; I will wait for v14 for this. I 
definitely see a number of logical bugs in the code that syncs mirror 
rules to vswitch db (as well as to sbdb).

A general note on test cases: while they seem more complete than before, 
there are more places for improvement: bulk test scenarios will need to 
expand to cover cases where the same port is attached and detached to a 
different number of mirrors in the same transaction; each new field of 
mirror resource (sink, type etc.) should be checked to make sure updates 
to OVN db propagate to SB and finally to vswitchd. You can probably 
identify other**lacunae better than me since you wrote the code.
**

One aspect of the patch that I'm not sure about is I-P engine updates. 
Someone more knowledgeable better review this part, not just me.

Looking forward to see v14 with updated test suite changes.

Thanks,
Ihar

On 11/4/22 3:09 PM, Abhiram R N wrote:
> Mirror creation just creates the mirror. The lsp-attach-mirror
> triggers the sequence to create Mirror in OVS DB on compute node.
> OVS already supports Port Mirroring.
>
> Note: This is targeted to mirror to destinations anywhere outside the
> cluster where the analyser resides and it need not be an OVN node.
>
> Example commands are as below:
>
> Mirror creation
> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>
> Attach a logical port to the mirror.
> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>
> Detach a source from Mirror
> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>
> Mirror deletion
> ovn-nbctl mirror-del mirror1
>
> Co-authored-by: Veda Barrenkala<vedabarrenkala@gmail.com>
> Signed-off-by: Veda Barrenkala<vedabarrenkala@gmail.com>
> Signed-off-by: Abhiram R N<abhiramrn@gmail.com>
> ---
> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
>               test to make it pass consistently.
> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
>
> V10 --> V11: Addressed review comments from V10 by Ihar
>             i) Expanded bulk updates test cases in ovn.at
>                Overall below cases are covered
>                a) Attaches multiple mirrors (new)
>                b) Equal detaches and attaches (same as V10)
>                c) Detaches more than attaches (new)
>                d) Attaches more than detaches (new)
>                e) Detaches all (new)
>            ii) Addressed the detach all case in mirror.c
>           iii) Minor correction in NEWS
>            iv) Added invalid mirror attach case in ovn-nbctl.at
>
> Files modified (V10 --> V11):
> Code --> mirror.c
> Test --> ovn.at, ovn-nbctl.at
> Misc --> NEWS
>
> Ihar,
>      Regarding mirror_delete function param delete_all it is wrt the
> port binding and if a port binding is removed we delete all its
> attachment. Already that use case is covered in ovn.at.
> Having said that the detaches all had issue in mirror_delete which
> I have addressed. With all the above cases added now in bulk updates
> hope it should give good assurance.
>
>   NEWS                        |   1 +
>   controller/automake.mk      |   4 +-
>   controller/mirror.c         | 538 +++++++++++++++++++++++++
>   controller/mirror.h         |  53 +++
>   controller/ovn-controller.c | 266 ++++++++++--
>   northd/en-northd.c          |   4 +
>   northd/inc-proc-northd.c    |   4 +
>   northd/northd.c             | 172 ++++++++
>   northd/northd.h             |   2 +
>   ovn-nb.ovsschema            |  31 +-
>   ovn-nb.xml                  |  63 +++
>   ovn-sb.ovsschema            |  26 +-
>   ovn-sb.xml                  |  50 +++
>   tests/ovn-nbctl.at          | 120 ++++++
>   tests/ovn-northd.at         | 102 +++++
>   tests/ovn.at                | 778 ++++++++++++++++++++++++++++++++++++
>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>   utilities/ovn-sbctl.c       |   4 +
>   18 files changed, 2547 insertions(+), 28 deletions(-)
>   create mode 100644 controller/mirror.c
>   create mode 100644 controller/mirror.h
>
> diff --git a/NEWS b/NEWS
> index 224a7b83e..84b22abdb 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>       any of LR's LRP IP, there is no need to create SNAT entry.  Now such
>       traffic destined to LRP IP is not dropped.
>     - Bump python version required for building OVN to 3.6.
> +  - Added Support for Remote Port Mirroring.
>   
>   OVN v22.06.0 - 03 Jun 2022
>   --------------------------
> diff --git a/controller/automake.mk b/controller/automake.mk
> index c2ab1bbe6..334672b4d 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>   	controller/ovsport.h \
>   	controller/ovsport.c \
>   	controller/vif-plug.h \
> -	controller/vif-plug.c
> +	controller/vif-plug.c \
> +	controller/mirror.h \
> +	controller/mirror.c
>   
>   controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
>   man_MANS += controller/ovn-controller.8
> diff --git a/controller/mirror.c b/controller/mirror.c
> new file mode 100644
> index 000000000..11f2b63a6
> --- /dev/null
> +++ b/controller/mirror.c
> @@ -0,0 +1,538 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <unistd.h>
> +
> +/* library headers */
> +#include "lib/sset.h"
> +#include "lib/util.h"
> +
> +/* OVS includes. */
> +#include "lib/vswitch-idl.h"
> +#include "openvswitch/vlog.h"
> +
> +/* OVN includes. */
> +#include "binding.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "mirror.h"
> +
> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> +
> +/* Static function declarations */
> +
> +static const struct ovsrec_port *
> +get_port_for_iface(const struct ovsrec_interface *iface,
> +                  const struct ovsrec_bridge *br_int)
> +{
> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *p = br_int->ports[i];
> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> +                return p;
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static bool
> +mirror_create(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    if (pb->n_up && !pb->up[0]) {
> +        return true;
> +    }
> +
> +    if (pb->chassis != pm_ctx->chassis_rec) {
> +        return true;
> +    }
> +
> +    if (!pm_ctx->ovs_idl_txn) {
> +        return false;
> +    }
> +
> +
> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
Please remove the log message, it will pollute the log file (please 
check for any other unnecessary log messages)
> +    /* Loop through the mirror rules */
> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> +        /* check if the mirror already exists in OVS DB */
> +        bool create_mirror = true;
> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
this is log(n^2), should we compute the names set once and then check 
against it?
> +                /* Mirror with same name already exists
> +                 * No need to create mirror
> +                 */
> +                create_mirror = false;
> +                break;
> +            }
> +        }
> +
> +        if (create_mirror) {
> +
> +            struct smap options = SMAP_INITIALIZER(&options);
> +            char *port_name, *key;
> +
> +            key = xasprintf("%ld",(long int) pb->mirror_rules[i]->index);
use %d and then you don't need to cast to long int.
> +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
> +            smap_add(&options, "key", key);
> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> +                /* Set the ERSPAN index */
> +                smap_add(&options, "erspan_idx", key);
> +                smap_add(&options, "erspan_ver","1");
> +
> +            }
> +            struct ovsrec_interface *iface =
> +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> +            port_name = xasprintf("ovn-%s",
> +                                   pb->mirror_rules[i]->name);
> +
> +            ovsrec_interface_set_name(iface, port_name);
> +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
> +            ovsrec_interface_set_options(iface, &options);

the code calculating the interface options map duplicates what's done in 
check_and_update_interface_table. Consider putting the code to construct 
the options map into a helper function.

> +
> +            struct ovsrec_port *port =
> +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_port_set_name(port, port_name);
> +            ovsrec_port_set_interfaces(port, &iface, 1);
> +
> +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> +
> +            smap_destroy(&options);
> +            free(port_name);
> +            free(key);
> +
> +            VLOG_INFO("Creating Mirror in OVS DB");
remove the log message
> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> +                                                             mirror);
> +        }
> +
> +        struct local_binding *lbinding = local_binding_find(
> +                               pm_ctx->local_bindings, pb->logical_port);
> +        const struct ovsrec_port *p =
> +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
> +        if (p) {
> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            } else {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            }
> +        }
> +    }
> +    return true;
> +}
> +
> +static void
> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> +                              struct ovsrec_mirror *ovs_mirror)
> +{
> +    char *filter;
> +    if ((ovs_mirror->n_select_dst_port)
> +            && (ovs_mirror->n_select_src_port)) {
> +        filter = "both";
> +    } else if (ovs_mirror->n_select_dst_port) {
> +        filter = "to-lport";
> +    } else {
> +        filter = "from-lport";
> +    }
nit: I would avoid strcmp below and instead introduce a enum for the 
filter type and then switch() over it below. Integer comparisons are 
cheaper than string.
> +
> +    if (strcmp(sb_mirror->filter, filter)) {
> +        if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"both")) {
add spaces after , - here and below (please fix the whole patch), I 
think these should have been captured by checkpatch script, no?
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"both")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> +                                   struct ovsrec_mirror *ovs_mirror)
please align function parameters here and in other places with spaces
> +{
> +    struct smap options = SMAP_INITIALIZER(&options);
> +    char *key, *type;
> +    struct ovsrec_interface *iface =
> +                          ovs_mirror->output_port->interfaces[0];
> +    struct smap *opts = &iface->options;
> +
> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> +    if (erspan_ver) {
> +        type = "erspan";
> +    } else {
> +        type = "gre";
> +    }
> +    if (strcmp(type, sb_mirror->type)) {
> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> +    }
> +
> +    key = xasprintf("%ld",(long int) sb_mirror->index);
Can't you just switch to "%d" and avoid (long int) casting?
> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> +    smap_add(&options, "key", key);
> +
> +    if (!strcmp(sb_mirror->type, "erspan")) {
> +        /* Set the ERSPAN index */
> +        smap_add(&options, "erspan_idx", key);
> +        smap_add(&options, "erspan_ver","1");
add space before "1"
> +    }
> +
> +    ovsrec_interface_set_options(iface, &options);
> +    smap_destroy(&options);
> +    free(key);
> +
remove the empty line
> +}
> +
> +static void
> +mirror_update(const struct sbrec_mirror *sb_mirror,
> +              struct ovsrec_mirror *ovs_mirror)
> +{
> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> +
nit: remove the empty line, it doesn't seem to serve any need
> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> +}
> +
> +static bool
> +mirror_delete(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx,
> +              struct shash *pb_mirror_map,
> +              bool delete_all)
> +{
> +
> +    if (!pm_ctx->ovs_idl_txn) {
> +        return false;
> +    }
> +
> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> +
> +    if (!delete_all) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> +        }
> +    }
> +
> +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +
> +            struct ovsrec_mirror *ovs_mirror = NULL;
> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> +                                            pb->mirror_rules[i]->name);
> +            if (ovs_mirror) {
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                               ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    struct shash_node *mirror_node;
> +    const struct sbrec_port_binding *sb_pb;
> +    int attach_cnt = 0;
it should be bool since you use it for two states only; also move its 
definition closer to where it's used (under if !sset_find branch)
> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> +            /* Find if the mirror has other sources */
> +            attach_cnt = 0;
> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
> +                                       pm_ctx->port_binding_table) {
if I understand it correctly, here for every pb, you walk through the 
whole list of pbs to check if any of them also use the mirror; making 
this operation very expensive. it seems to me that a proper structure 
like a map of mirror-to-(set of port names) should be used to avoid this.
> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
> +                                                ovs_mirror->name)) {
> +                        attach_cnt++;
...and you could probably bail out from here since the only thing you 
really care is that at least one other pb is attached to the mirror
> +                    }
> +                }
> +            }
> +            if (attach_cnt) {
> +                /* More than 1 source then just
> +                 * update the mirror table
> +                 */
> +                bool done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
> +                                                   && (done == false)); i++) {
transform done == false condition into if (done) break at the end of the 
for-loop
> +                    const struct ovsrec_port *port_rec =
> +                                               ovs_mirror->select_dst_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_dst_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +                done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
> +                                                   && (done == false)); i++) {
> +                    const struct ovsrec_port *port_rec =
> +                                                ovs_mirror->select_src_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_src_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +            } else {
> +                /*
> +                 * If only 1 source delete the output port
> +                 * and then delete the mirror completely
> +                 */
> +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    const char *used_node, *used_next;
> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> +    }
> +    sset_destroy(&pb_mirrors);
> +
> +    return true;
> +}
> +
> +static void
> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> +                            struct port_mirror_ctx *pm_ctx,
> +                            struct shash *pb_mirror_map)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +void
> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> +{
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> +
> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> +}
> +
> +
> +void
> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> +{
> +    shash_init(ovs_mirrors);
> +}
> +
> +void
> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> +                                       pm_ctx->port_binding_table) {
> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> +    }
> +}
> +
> +bool
> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
> +                     struct port_mirror_ctx *pm_ctx)
> +{
> +    bool ret = true;
> +    struct local_binding *lbinding = local_binding_find(
> +                               pm_ctx->local_bindings, pb->logical_port);
> +
> +    if (strcmp(pb->type, "") && (!lbinding)) {
> +        return ret;
> +    }
> +
> +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> +
> +    /* Need to find if mirror needs update */
> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> +    if (!removed) {
> +        if ((pb->n_mirror_rules == 0)
> +              && (shash_is_empty(&port_ovs_mirrors))) {
> +            /* No mirror update */
I think this branch is redundant; the next one will do exactly the same 
for the case of n_mirror_rules = 0
> +        } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) {
> +            /* Though number of mirror rules are same,
"Though the number of mirror rules is the same, need to verify the contents"
> +             * need to verify the contents
> +             */
> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> +                if (!shash_find(&port_ovs_mirrors,
> +                               pb->mirror_rules[i]->name)) {
> +                    /* Mis match in OVN SB DB and OVS DB
> +                     * Delete and Create mirror(s) with proper sources
> +                     */
> +                    ret = mirror_delete(pb, pm_ctx,
> +                                        &port_ovs_mirrors, false);
> +                    if (ret) {
> +                        ret = mirror_create(pb, pm_ctx);
> +                    }
> +                    break;
> +                }
> +            }
> +        } else {
please flatten this out so that there's a single if-else layer for all cases
> +            /* Update Mirror */
> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> +                /* create mirror,
> +                 * if mirror already exists only update selection
> +                 */
> +                ret = mirror_create(pb, pm_ctx);
I still feel this is incorrect. The fact that pb is attached to a larger 
number of mirrors doesn't necessarily mean that it was *only* attached 
to new mirrors in this transaction, and that it was *not* detached from 
other mirrors in the same transaction. Consider that in the same 
transaction, a port binding was attached to 2 more mirrors AND detached 
from 1 existing mirror; in this case, only mirror_create() will be 
called, so the old attachment won't be cleared.
> +            } else {
> +                /* delete mirror,
> +                 * if mirror has other sources only update selection
> +                 */
> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);

the same problem here: even if the number of mirror attachments reduced, 
it doesn't necessarily mean that the port was not attached to new 
mirrors as part of the transaction.

I've looked through your test cases and I don't see a single case where 
you would detach / attach a different number of mirrors to THE SAME port 
in scope of a single transaction. Please add the test cases to cover 
this code.

> +            }
> +        }
> +    } else {
> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
I'm puzzled by this branch. So if a pb is removed, then you are going to 
delete all mirrors that were attached to it (delete_all=true) regardless 
of any other ports relying on the mirror. Am I interpreting it 
correctly? If so, why is it the right thing to do? Perhaps adding some 
more comments in this function explaining which branches handle which 
scenarios would help me understand what's happening. Thanks.
> +    }
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              &port_ovs_mirrors) {
> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(&port_ovs_mirrors);
> +
> +    return ret;
> +}
> +
> +bool
> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_mirror *mirror = NULL;
> +    struct ovsrec_mirror *ovs_mirror = NULL;
> +
> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
> +    /* For each tracked mirror entry check if OVS entry is there*/
nit: add space before */
> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
> +        if (ovs_mirror) {
> +            if (sbrec_mirror_is_deleted(mirror)) {
> +                /* Need to delete the mirror in OVS */
> +                VLOG_INFO("Delete mirror and remove port");
remove this log message, it will pollute log file unnecessarily
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                      ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            } else {
> +                mirror_update(mirror, ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +void
> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> +{
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              ovs_mirrors) {
> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(ovs_mirrors);
> +}
> diff --git a/controller/mirror.h b/controller/mirror.h
> new file mode 100644
> index 000000000..85b964f55
> --- /dev/null
> +++ b/controller/mirror.h
> @@ -0,0 +1,53 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_MIRROR_H
> +#define OVN_MIRROR_H 1
> +
> +struct ovsdb_idl_txn;
> +struct ovsrec_port_table;
> +struct ovsrec_bridge;
> +struct ovsrec_bridge_table;
> +struct ovsrec_open_vswitch_table;
> +struct sbrec_chassis;
> +struct ovsrec_interface_table;
> +struct ovsrec_mirror_table;
> +struct sbrec_mirror_table;
> +struct sbrec_port_binding_table;
> +
> +struct port_mirror_ctx {
> +    struct shash *ovs_mirrors;
> +    struct ovsdb_idl_txn *ovs_idl_txn;
> +    const struct ovsrec_port_table *port_table;
> +    const struct ovsrec_bridge *br_int;
> +    const struct sbrec_chassis *chassis_rec;
> +    const struct ovsrec_bridge_table *bridge_table;
> +    const struct ovsrec_open_vswitch_table *ovs_table;
> +    const struct ovsrec_interface_table *iface_table;
> +    const struct ovsrec_mirror_table *mirror_table;
> +    const struct sbrec_mirror_table *sb_mirror_table;
> +    const struct sbrec_port_binding_table *port_binding_table;
> +    struct shash *local_bindings;
> +};
> +
> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> +void ovn_port_mirror_init(struct shash *);
> +void ovn_port_mirror_destroy(struct shash *);
> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> +                                  bool removed,
> +                                  struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> +#endif
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 8895c7a2b..15ab17c4a 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -78,6 +78,7 @@
>   #include "lib/inc-proc-eng.h"
>   #include "lib/ovn-l7.h"
>   #include "hmapx.h"
> +#include "mirror.h"
>   
>   VLOG_DEFINE_THIS_MODULE(main);
>   
> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
> +    mirror_register_ovs_idl(ovs_idl);
>   }
>   
>   #define SB_NODES \
> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>       SB_NODE(load_balancer, "load_balancer") \
>       SB_NODE(fdb, "fdb") \
>       SB_NODE(meter, "meter") \
> +    SB_NODE(mirror, "mirror") \
>       SB_NODE(static_mac_binding, "static_mac_binding")
>   
>   enum sb_engine_node {
> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>       OVS_NODE(bridge, "bridge") \
>       OVS_NODE(port, "port") \
>       OVS_NODE(interface, "interface") \
> -    OVS_NODE(qos, "qos")
> +    OVS_NODE(qos, "qos") \
> +    OVS_NODE(mirror, "mirror")
>   
>   enum ovs_engine_node {
>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>       free(lbs);
>   }
>   
> +/* Mirror Engine */
> +struct ed_type_port_mirror {
> +    struct shash ovs_mirrors;
> +};
> +
> +static void *
> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> +                    struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> +    return port_mirror;
> +}
> +
> +static void
> +en_port_mirror_cleanup(void *data)
> +{
> +    struct ed_type_port_mirror *port_mirror = data;
> +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> +}
> +
> +static void
> +init_port_mirror_ctx(struct engine_node *node,
> +                 struct ed_type_runtime_data *rt_data,
> +                 struct ed_type_port_mirror *port_mirror_data,
> +                 struct port_mirror_ctx *pm_ctx)
> +{
> +    struct ovsrec_open_vswitch_table *ovs_table =
> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_open_vswitch", node));
> +    struct ovsrec_bridge_table *bridge_table =
> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_bridge", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
> +
> +    ovs_assert(br_int && chassis_id);
> +    const struct sbrec_chassis *chassis = NULL;
> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_chassis", node),
> +                "name");
> +
> +    if (chassis_id) {
> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> +    }
> +    ovs_assert(chassis);
> +
> +    struct ovsrec_port_table *port_table =
> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_port", node));
> +
> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> +        engine_get_input_data("ovs_interface_shadow", node);
> +
> +    struct ovsrec_mirror_table *mirror_table =
> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_mirror", node));
> +
> +    struct sbrec_port_binding_table *pb_table =
> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
> +            engine_get_input("SB_port_binding", node));
> +
> +    struct sbrec_mirror_table *sb_mirror_table =
> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
> +            engine_get_input("SB_mirror", node));
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                           &port_mirror_data->ovs_mirrors) {
> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> +    }
> +
> +    const struct ovsrec_mirror *ovsmirror = NULL;
> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
> +    }
> +
> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> +    pm_ctx->port_table = port_table;
> +    pm_ctx->iface_table = iface_shadow->iface_table;
> +    pm_ctx->mirror_table = mirror_table;
> +    pm_ctx->port_binding_table = pb_table;
> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> +    pm_ctx->br_int = br_int;
> +    pm_ctx->chassis_rec = chassis;
> +    pm_ctx->bridge_table = bridge_table;
> +    pm_ctx->ovs_table = ovs_table;
> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> +}
> +
> +static void
> +en_port_mirror_run(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    ovn_port_mirror_run(&pm_ctx);
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +static bool
> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    /* There is no tracked data. Fall back to full recompute of port_mirror */
> +    if (!rt_data->tracked) {
> +        return false;
> +    }
> +
> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> +    if (hmap_is_empty(tracked_dp_bindings)) {
> +        return true;
> +    }
> +
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    struct tracked_datapath *tdp;
> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> +            /* Fall back to full recompute when a local datapath
> +             * is added or deleted. */
> +            return false;
> +        }
> +
> +        struct shash_node *shash_node;
> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> +            struct tracked_lport *lport = shash_node->data;
> +            bool removed =
> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
> +static bool
> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle port binding updates (i.,e when the mirror column

"i.e."

> +     * of port_binding is updated)
> +     */
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +                                               pm_ctx.port_binding_table) {
> +        bool removed = sbrec_port_binding_is_deleted(pb);
> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> +            return false;
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
> +static bool
> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle sb mirror updates
> +     */
nit: combine two lines above
> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> +        return false;
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
>   /* Engine node which is used to handle the Non VIF data like
>    *   - OVS patch ports
>    *   - Tunnel ports and the related chassis information.
> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>       ENGINE_NODE(northd_options, "northd_options");
>       ENGINE_NODE(dhcp_options, "dhcp_options");
> +    ENGINE_NODE(port_mirror, "port_mirror");
>   
>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>       SB_NODES
> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>       engine_add_input(&en_flow_output, &en_pflow_output,
>                        flow_output_pflow_output_handler);
>   
> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> +                     engine_noop_handler);
> +    engine_add_input(&en_flow_output, &en_port_mirror,
> +                     engine_noop_handler);
perhaps I am missing something, but why would flow_output depend on 
mirror updates, when it seems like the only action needed is mapping ovn 
mirror definitions to ovs mirror table?
> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> +                     port_mirror_runtime_data_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> +                     port_mirror_sb_mirror_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> +                     port_mirror_port_binding_handler);
> +
>       struct engine_arg engine_arg = {
>           .sb_idl = ovnsb_idl_loop.idl,
>           .ovs_idl = ovs_idl_loop.idl,
> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>   
>                       stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>                                       time_msec());
> -                    if (ovnsb_idl_txn) {
> -                        if (ofctrl_has_backlog()) {
> -                            /* When there are in-flight messages pending to
> -                             * ovs-vswitchd, we should hold on recomputing so
> -                             * that the previous flow installations won't be
> -                             * delayed.  However, we still want to try if
> -                             * recompute is not needed and we can quickly
> -                             * incrementally process the new changes, to avoid
> -                             * unnecessarily forced recomputes later on.  This
> -                             * is because the OVSDB change tracker cannot
> -                             * preserve tracked changes across iterations.  If
> -                             * change tracking is improved, we can simply skip
> -                             * this round of engine_run and continue processing
> -                             * acculated changes incrementally later when
> -                             * ofctrl_has_backlog() returns false. */
> -                            engine_run(false);
> -                        } else {
> -                            engine_run(true);
> -                        }
> -                    } else {
> -                        /* Even if there's no SB DB transaction available,
> +
> +                    bool allow_engine_recompute = true;
> +
> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
> +                                                     ofctrl_has_backlog()) {
> +                        /* When there are in-flight messages pending to
> +                         * ovs-vswitchd, we should hold on recomputing so
> +                         * that the previous flow installations won't be
> +                         * delayed.  However, we still want to try if
> +                         * recompute is not needed and we can quickly
> +                         * incrementally process the new changes, to avoid
> +                         * unnecessarily forced recomputes later on.  This
> +                         * is because the OVSDB change tracker cannot
> +                         * preserve tracked changes across iterations.  If
> +                         * change tracking is improved, we can simply skip
> +                         * this round of engine_run and continue processing
> +                         * acculated changes incrementally later when
> +                         * ofctrl_has_backlog() returns false. */
> +
> +                        /* Even if there's no SB/OVS DB transaction available,
>                            * try to run the engine so that we can handle any
>                            * incremental changes that don't require a recompute.
>                            * If a recompute is required, the engine will abort,
>                            * triggerring a full run in the next iteration.
>                            */
> -                        engine_run(false);
> +                        allow_engine_recompute = false;
>                       }
> +
> +                    engine_run(allow_engine_recompute);
> +
>                       stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>                                      time_msec());
>                       if (engine_has_updated()) {
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 7fe83db64..608220b1f 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void *data)
>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
>       input_data.nbrec_static_mac_binding_table =
>           EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> +    input_data.nbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>   
>       input_data.sbrec_sb_global_table =
>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, void *data)
>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>       input_data.sbrec_static_mac_binding_table =
>           EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> +    input_data.sbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>   
>       northd_run(&input_data, data,
>                  eng_ctx->ovnnb_idl_txn,
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 54e0ad3b0..ac27a730e 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>       NB_NODE(acl, "acl") \
>       NB_NODE(logical_router, "logical_router") \
>       NB_NODE(qos, "qos") \
> +    NB_NODE(mirror, "mirror") \
>       NB_NODE(meter, "meter") \
>       NB_NODE(meter_band, "meter_band") \
>       NB_NODE(logical_router_port, "logical_router_port") \
> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>       SB_NODE(logical_flow, "logical_flow") \
>       SB_NODE(logical_dp_group, "logical_DP_group") \
>       SB_NODE(multicast_group, "multicast_group") \
> +    SB_NODE(mirror, "mirror") \
>       SB_NODE(meter, "meter") \
>       SB_NODE(meter_band, "meter_band") \
>       SB_NODE(datapath_binding, "datapath_binding") \
> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>       engine_add_input(&en_northd, &en_nb_acl, NULL);
>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>       engine_add_input(&en_northd, &en_nb_qos, NULL);
> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>       engine_add_input(&en_northd, &en_nb_meter, NULL);
>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>       engine_add_input(&en_northd, &en_sb_meter, NULL);
>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> diff --git a/northd/northd.c b/northd/northd.c
> index b7388afc5..52abdda28 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>       free(requested_chassis_sb);
>   }
>   
> +static void
> +do_sb_mirror_addition(struct northd_input *input_data,
> +                      const struct ovn_port *op)
> +{
> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +        const struct sbrec_mirror *sb_mirror;
> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> +                                     input_data->sbrec_mirror_table) {
> +            if (!strcmp(sb_mirror->name,
> +                        op->nbsp->mirror_rules[i]->name)) {
> +                /* Add the value to SB */
> +                sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> +                                                                sb_mirror);
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
> +                                       const struct ovn_port *op)
> +{
> +    size_t i = 0;
> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> +        /* Needs deletion in SB */
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
this will only delete old mirror rules; but it won't add any new rules
> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
> +        /* Needs addition in SB */
> +        do_sb_mirror_addition(input_data, op);
vice versa here: it will only add new rules but not remove obsolete rules
> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
> +        /*
> +         * Check if its the same mirrors on both SB and NB DBs
> +         * If not update accordingly.
> +         */
> +        bool needs_sb_addition = false;
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
> +                    needs_sb_addition = true;
> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +        if (needs_sb_addition) {
> +            do_sb_mirror_addition(input_data, op);
> +        }
> +    }
> +}
> +
>   static void
>   ovn_port_update_sbrec(struct northd_input *input_data,
>                         struct ovsdb_idl_txn *ovnsb_txn,
> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>           }
>           sbrec_port_binding_set_external_ids(op->sb, &ids);
>           smap_destroy(&ids);
> +
> +        if (!op->nbsp->n_mirror_rules) {
> +            /* Nothing is set. Clear mirror_rules from pb. */
> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> +        } else {
> +            /* Check if SB DB update needed */
> +            sbrec_port_binding_update_mirror_rules(input_data, op);
> +        }
> +
>       }
>       if (op->tunnel_key != op->sb->tunnel_key) {
>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>       shash_destroy(&sb_meters);
>   }
>   
> +static bool
> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> +                  const struct sbrec_mirror *sb_mirror)
> +{
> +
> +    if (nb_mirror->index != sb_mirror->index) {
> +        return true;
> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static void
> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> +                             const char *mirror_name,
> +                             const struct nbrec_mirror *nb_mirror,
> +                             struct shash *sb_mirrors,
> +                             struct sset *used_sb_mirrors)
> +{
> +    const struct sbrec_mirror *sb_mirror;
> +    bool new_sb_mirror = false;
> +
> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> +    if (!sb_mirror) {
> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> +        new_sb_mirror = true;
> +    }
> +    sset_add(used_sb_mirrors, mirror_name);
> +
> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
> +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> +    }
> +}
> +
> +static void
> +sync_mirrors(struct northd_input *input_data,
> +            struct ovsdb_idl_txn *ovnsb_txn)
> +{
> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> +
> +    const struct sbrec_mirror *sb_mirror;
> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> +    }
> +
> +    const struct nbrec_mirror *nb_mirror;
> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
> +                                     &sb_mirrors, &used_sb_mirrors);
> +    }
> +
> +    const char *used_mirror;
> +    const char *used_mirror_next;
> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
> +        shash_find_and_delete(&sb_mirrors, used_mirror);
> +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
> +    }
> +    sset_destroy(&used_sb_mirrors);
> +
> +    struct shash_node *node, *next;
> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> +        sbrec_mirror_delete(node->data);
> +        shash_delete(&sb_mirrors, node);
> +    }
> +    shash_destroy(&sb_mirrors);
> +}
> +
>   /*
>    * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>    * and Southbound db.
> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> +    sync_mirrors(input_data, ovnsb_txn);
>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> diff --git a/northd/northd.h b/northd/northd.h
> index da90e2815..17a62ea10 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -36,6 +36,7 @@ struct northd_input {
>       const struct nbrec_acl_table *nbrec_acl_table;
>       const struct nbrec_static_mac_binding_table
>           *nbrec_static_mac_binding_table;
> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>   
>       /* Southbound table references */
>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
> @@ -55,6 +56,7 @@ struct northd_input {
>       const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>       const struct sbrec_static_mac_binding_table
>           *sbrec_static_mac_binding_table;
> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>   
>       /* Indexes */
>       struct ovsdb_idl_index *sbrec_chassis_by_name;
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 174364c8b..01de55222 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>   {
>       "name": "OVN_Northbound",
> -    "version": "6.3.0",
> -    "cksum": "4042813038 31869",
> +    "version": "6.4.0",
> +    "cksum": "589874483 33352",
>       "tables": {
>           "NB_Global": {
>               "columns": {
> @@ -132,6 +132,11 @@
>                                               "refType": "weak"},
>                                    "min": 0,
>                                    "max": 1}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                   "ha_chassis_group": {
>                       "type": {"key": {"type": "uuid",
>                                        "refTable": "HA_Chassis_Group",
> @@ -301,6 +306,28 @@
>                       "type": {"key": "string", "value": "string",
>                                "min": 0, "max": "unlimited"}}},
>               "isRoot": false},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["from-lport",
> +                                                             "to-lport",
> +                                                             "both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["gre",
> +                                                             "erspan"]]}}},
> +                "index": {"type": "integer"},
> +                "src": {"type": {"key": {"type": "uuid",
> +                                           "refTable": "Logical_Switch_Port",
> +                                           "refType": "weak"},
> +                                   "min": 0,
> +                                   "max": "unlimited"}},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>           "Meter": {
>               "columns": {
>                   "name": {"type": "string"},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index f41e9d7c0..d8730c8fc 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1554,6 +1554,11 @@
>         </column>
>       </group>
>   
> +    <column name="mirror_rules">
> +        Mirror rules that apply to logical switch port which is the source.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>       <column name="ha_chassis_group">
>         References a row in the OVN Northbound database's
>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> @@ -2491,6 +2496,64 @@
>       </column>
>     </table>
>   
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
> +      the <ref table="Logical_Switch_Port"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.
> +        Supported values for filter to-lport / from-lport / both
> +        to-lport - to mirror packets coming into logical port
> +        from-lport - to mirror packets going out of logical port
> +        both - to mirror packets coming into and going out of logical port.
> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.
> +        The value it takes is an IP address of the sink port.
> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets. Supported Tunnel types gre and erspan
> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the tunnel ID. Depending on the
> +        tunnel type configured, GRE key value if type GRE and erspan_idx value
> +        if ERSPAN
> +      </p>
> +    </column>
> +
> +    <column name="src">
> +      <p>
> +        The value of this field represents a list of source ports for the
> +        mirror. Please see the <ref table="Logical_Switch_Port"/> table.
> +      </p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
> +
>     <table name="Meter" title="Meter entry">
>       <p>
>         Each row in this table represents a meter that can be used for QoS or
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 576ebbdeb..b83134416 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>   {
>       "name": "OVN_Southbound",
> -    "version": "20.25.0",
> -    "cksum": "53184112 28845",
> +    "version": "20.26.0",
> +    "cksum": "2344151793 30004",
>       "tables": {
>           "SB_Global": {
>               "columns": {
> @@ -142,6 +142,23 @@
>               "indexes": [["datapath", "tunnel_key"],
>                           ["datapath", "name"]],
>               "isRoot": true},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["from-lport",
> +                                                      "to-lport","both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["gre", "erspan"]]}}},
> +                "index": {"type": "integer"},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>           "Meter": {
>               "columns": {
>                   "name": {"type": "string"},
> @@ -230,6 +247,11 @@
>                                                         "refTable": "Encap",
>                                                         "refType": "weak"},
>                                       "min": 0, "max": "unlimited"}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                   "mac": {"type": {"key": "string",
>                                    "min": 0,
>                                    "max": "unlimited"}},
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 315d60853..05c0db6b4 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>       </column>
>     </table>
>   
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Port_Binding"/> column in
> +      the <ref table="Port_Binding"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets
> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the key/idx depending on the
> +        tunnel type configured
> +      </p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
> +
>     <table name="Meter" title="Meter entry">
>       <p>
>         Each row in this table represents a meter that can be used for QoS or
> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>         </column>
>       </group>
>   
> +    <column name="mirror_rules">
> +        Mirror rules that apply to the port binding.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>       <group title="Patch Options">
>         <p>
>           These options apply to logical ports with <ref column="type"/> of
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 4d480e357..d79f9d929 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>   
>   dnl ---------------------------------------------------------------------
>   
> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> +AT_CHECK([ovn-nbctl ls-add sw0])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> +
> +dnl Add duplicate mirror name
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> +
> +dnl Attach invalid source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> +
> +dnl Attach source port to invalid mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], [stderr])
> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
> +
> +dnl Attach source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> +
> +dnl Attach one more source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> +
> +dnl Verify if multiple ports are attached to the same mirror properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +mirror3:
> +  Type     :  gre
> +  Sink     :  10.10.10.3
> +  Filter   :  to-lport
> +  Index/Key:  2
> +  Sources  :  sw0-port1  sw0-port3
> +])
> +
> +dnl Detach one source port from mirror
> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> +
> +dnl Verify if detach source port from mirror happens properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +mirror3:
> +  Type     :  gre
> +  Sink     :  10.10.10.3
> +  Filter   :  to-lport
> +  Index/Key:  2
> +  Sources  :  sw0-port1
> +])
> +
> +dnl Delete a single mirror which has source attached.
> +AT_CHECK([ovn-nbctl mirror-del mirror3])
> +
> +dnl Check if the detach happened from source properly
> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules |  cut -b 3], [0], [dnl
> +
> +])
> +
> +dnl Check if the mirror deleted properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +])
> +
> +dnl Delete another mirror
> +AT_CHECK([ovn-nbctl mirror-del mirror2])
> +
> +dnl Update the Sink address
> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> +
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  192.168.1.13
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +])
> +
> +dnl Delete all mirrors
> +AT_CHECK([ovn-nbctl mirror-del])
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +])])
> +
> +dnl ---------------------------------------------------------------------
> +
>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>   AT_CHECK([ovn-nbctl lr-add lr0])
>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 4f399eccb..4e6c268e4 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>   AT_CLEANUP
>   ])
>   
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Check NB-SB mirrors sync])
> +AT_KEYWORDS([mirrors])
> +ovn_start
> +
> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"10.10.10.2"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +erspan
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . sink=192.168.1.13
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +erspan
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . type=gre
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . index=12
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +12
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . filter=to-lport
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +to-lport
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +12
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CLEANUP
> +])
> +
>   OVN_FOR_EACH_NORTHD_NO_HV([
>   AT_SETUP([ACL skip hints for stateless config])
>   AT_KEYWORDS([acl])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index f8b8db4df..cd5527ea1 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>   AT_CLEANUP
>   ])
>   
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror])
> +AT_KEYWORDS([Mirror])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +ovn-nbctl dump-flows > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +
> +for i in 1 2; do
> +    : > vif$i.expected
> +done
> +
> +net_add n2
> +
> +sim_add hv2
> +as hv2
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> +ovn_attach n2 br-phys 192.168.1.12
> +
> +OVN_POPULATE_ARP
> +
> +as hv1
> +
> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> +#
> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> +# provided, then it should be the ip and icmp checksums of the packet
> +# responded; otherwise, no reply is expected.
> +# In the absence of an ip checksum calculation helpers, this relies
> +# on the caller to provide the checksums for the ip and icmp headers.
> +# XXX This should be more systematic.
> +#
> +# INPORT is an lport number, e.g. 11 for vif11.
> +# ETH_SRC and ETH_DST are each 12 hex digits.
> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> +# ENCAP_TYPE - gre or erspan
> +# FILTER - Mirror Filter - to-lport / from-lport
> +test_ipv4_icmp_request() {
> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
> +    shift; shift; shift; shift; shift; shift; shift
> +    shift; shift; shift; shift;
> +
> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> +    local ip_ttl=02
> +    local icmp_id=5fbf
> +    local icmp_seq=0001
> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> +    local icmp_type_code_request=0800
> +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> +
> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> +
> +    # Expect to receive the reply, if any. In same port where packet was sent.
> +    # Note: src and dst fields are expected to be reversed.
> +    local icmp_type_code_response=0000
> +    local reply_icmp_ttl=fe
> +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> +    echo $reply >> vif$inport.expected
> +    local remote_mac=000000020200
> +    local local_mac=000000010200
> +    if test ${mirror_encap_type} = "gre" ; then
> +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> +        fi
> +    elif test ${mirror_encap_type} = "erspan" ; then
> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> +        local erspan_seq0=100088be000000001000000000000000
> +        local erspan_seq1=100088be000000011000000000000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> +        fi
> +    fi
> +    echo $mirror >> br-phys_n1.expected
> +
> +}
> +
> +# Set IPs
> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> +l1_ip=$(ip_to_hex 192 168 1 2)
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=erspan
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . filter=from-lport
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=gre
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +echo "---------OVN NB Mirror-----"
> +ovn-nbctl mirror-list
> +
> +echo "---------OVS Mirror----"
> +ovs-vsctl list Mirror
> +
> +echo "-----------------------"
> +
> +echo "Verifying Mirror deletion in OVS"
> +# Set vif1 iface-id such that OVN releases port binding
> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> +])
> +
> +# Set vif1 iface-id back to ls1-lp1
> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
> +
> +# Delete vif1 so that OVN releases port binding
> +check ovs-vsctl del-port br-int vif1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk swap attachments])
> +AT_KEYWORDS([Mirror test bulk swap attachments])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +# Equal detaches and attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk attach multiple])
> +AT_KEYWORDS([Mirror test bulk attach multiple])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +
> +# Attaches multiple mirrors
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk more detach and less attach])
> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl --wait=hv sync
> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl --wait=hv sync
> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +
> +check ovn-nbctl --wait=hv sync
> +
> +# Detaches more than attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$origB" = "$new2"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk attach more than detach])
> +AT_KEYWORDS([Mirror test bulk attach more than detach])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check ovn-nbctl --wait=hv sync
> +
> +# Attaches more than detaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk detach multiple])
> +AT_KEYWORDS([Mirror test bulk detach multiple])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +# Detaches all
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
>   
>   OVN_FOR_EACH_NORTHD([
>   AT_SETUP([Port Groups])
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 811468dc6..af2e61435 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -271,6 +271,19 @@ QoS commands:\n\
>                               remove QoS rules from SWITCH\n\
>     qos-list SWITCH           print QoS rules for SWITCH\n\
>   \n\
> +Mirror commands:\n\
> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> +                            add a mirror with given name\n\
> +                            specify TYPE 'gre' or 'erspan'\n\
> +                            specify the tunnel INDEX value\n\
> +                                (indicates key if GRE\n\
> +                                 erpsan_idx if ERSPAN)\n\
> +                            specify FILTER for mirroring selection\n\
> +                                'to-lport' / 'from-lport' / 'both'\n\
> +                            specify Sink / Destination i.e. Remote IP\n\
> +  mirror-del [NAME]         remove mirrors\n\
> +  mirror-list               print mirrors\n\
> +\n\
>   Meter commands:\n\
>     [--fair]\n\
>     meter-add NAME ACTION RATE UNIT [BURST]\n\
> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>                               set dhcpv6 options for PORT\n\
>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>     lsp-get-ls PORT           get the logical switch which the port belongs to\n\
> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>   \n\
>   Forwarding group commands:\n\
>     [--liveness]\n\
> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>       ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>   }
>   
> +static void
> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_switch_port_col_mirror_rules);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static int
> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> +{
> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> +
> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> +
> +    return strcmp(mirror1->name,mirror2->name);
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> +                    bool must_exist,
> +                    const struct nbrec_mirror **mirror_p)
> +{
> +    const struct nbrec_mirror *mirror = NULL;
> +    *mirror_p = NULL;
> +
> +    struct uuid mirror_uuid;
> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> +    if (is_uuid) {
> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> +    }
> +
> +    if (!mirror) {
> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +            if (!strcmp(mirror->name, id)) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    if (!mirror && must_exist) {
> +        return xasprintf("%s: mirror %s not found",
> +                         id, is_uuid ? "UUID" : "name");
> +    }
> +
> +    *mirror_p = mirror;
> +    return NULL;
> +}
> +
> +static void
> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Check if same mirror rule already exists for the lsp */
> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +            if (!may_exist) {
> +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
> +                          ctx->argv[1]);
> +                return;
> +            }
> +            return;
> +        }
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> +
> +}
> +
> +static void
> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> +
> +}
> +
>   static void
>   nbctl_lsp_set_type(struct ctl_context *ctx)
>   {
> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
>   }
>   
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_filter(const char *arg, const char **selection_p)
> +{
> +    /* Validate selection.  Only require the first letter. */
> +    if (arg[0] == 't') {
> +        *selection_p = "to-lport";
> +    } else if (arg[0] == 'f') {
> +        *selection_p = "from-lport";
> +    } else if (arg[0] == 'b') {
> +        *selection_p = "both";
> +    } else {
> +        *selection_p = NULL;
> +        return xasprintf("%s: selection must be \"to-lport\" or "
> +                         "\"from-lport\" or \"both\" ", arg);
> +    }
> +    return NULL;
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_type(const char *arg, const char **type_p)
> +{
> +    /* Validate type.  Only require the first letter. */
> +    if (arg[0] == 'g') {
> +        *type_p = "gre";
> +    } else if (arg[0] == 'e') {
> +        *type_p = "erspan";
> +    } else {
> +        *type_p = NULL;
> +        return xasprintf("%s: type must be \"gre\" or "
> +                         "\"erspan\"", arg);
> +    }
> +    return NULL;
> +}
> +
> +static void
> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +}
> +
> +static void
> +nbctl_mirror_add(struct ctl_context *ctx)
> +{
> +    const char *filter = NULL;
> +    const char *sink_ip = NULL;
> +    const char *type = NULL;
> +    const char *name = NULL;
> +    char *new_sink_ip = NULL;
> +    int64_t index;
> +    char *error = NULL;
> +    const struct nbrec_mirror *mirror_check = NULL;
> +
> +    /* Mirror Name */
> +    name = ctx->argv[1];
> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> +        if (!strcmp(mirror_check->name, name)) {
> +            ctl_error(ctx, "Mirror with %s name already exists.",
> +                      name);
> +            return;
> +        }
> +    }
> +
> +    /* Tunnel Type - GRE/ERSPAN */
> +    error = parse_type(ctx->argv[2], &type);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* tunnel index / GRE key / ERSPAN idx */
> +    index = atoi(ctx->argv[3]);
> +
> +    /* Filter for mirroring */
> +    error = parse_filter(ctx->argv[4], &filter);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Destination / Sink details */
> +    sink_ip = ctx->argv[5];
> +
> +    /* check if it is a valid ip */
> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
> +    if (!new_sink_ip) {
> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
> +    }
> +
> +    if (new_sink_ip) {
> +        free(new_sink_ip);
> +    } else {
> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> +        return;
> +    }
> +
> +    /* Create the mirror. */
> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> +    nbrec_mirror_set_name(mirror, name);
> +    nbrec_mirror_set_index(mirror, index);
> +    nbrec_mirror_set_filter(mirror, filter);
> +    nbrec_mirror_set_type(mirror, type);
> +    nbrec_mirror_set_sink(mirror, sink_ip);
> +
> +}
> +
> +static void
> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_mirror *mirror, *next;
> +
> +    /* If a name is not specified, delete all mirrors. */
> +    if (ctx->argc == 1) {
> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> +            nbrec_mirror_delete(mirror);
> +        }
> +        return;
> +    }
> +
> +    /* Remove the matching mirror. */
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (strcmp(ctx->argv[1], mirror->name)) {
> +            continue;
> +        }
> +        nbrec_mirror_delete(mirror);
> +        return;
> +    }
> +}
> +
> +static void
> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_list(struct ctl_context *ctx)
> +{
> +
> +    const struct nbrec_mirror **mirrors = NULL;
> +    const struct nbrec_mirror *mirror;
> +    size_t n_capacity = 0;
> +    size_t n_mirrors = 0;
> +
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (n_mirrors == n_capacity) {
> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
> +        }
> +
> +        mirrors[n_mirrors] = mirror;
> +        n_mirrors++;
> +    }
> +
> +    if (n_mirrors) {
> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> +    }
> +
> +    for (size_t i = 0; i < n_mirrors; i++) {
> +        mirror = mirrors[i];
> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> +        /* print all the values */
> +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
> +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
> +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
> +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
> +                                                (long int) mirror->index);
> +        ds_put_cstr(&ctx->output,   "  Sources  :");
> +        if (mirror->n_src > 0) {
> +            struct svec srcs;
> +            const char *src;
> +            size_t j;
> +            svec_init(&srcs);
> +            for (j = 0; j < mirror->n_src; j++) {
> +                svec_add(&srcs, mirror->src[j]->name);
> +            }
> +            svec_sort(&srcs);
> +            SVEC_FOR_EACH (j, src, &srcs) {
> +                ds_put_format(&ctx->output, "  %s", src);
> +            }
> +            svec_destroy(&srcs);
> +        } else {
> +            ds_put_cstr(&ctx->output, "  None attached");
> +        }
> +        ds_put_cstr(&ctx->output, "\n");
> +    }
> +
> +    free(mirrors);
> +}
> +
>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
>       = {{&nbrec_logical_switch_port_col_name, NULL,
> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>         NULL, "", RO },
>   
> +    /* mirror commands. */
> +    { "mirror-add", 5, 5,
> +      "NAME TYPE INDEX FILTER IP",
> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
> +    { "mirror-del", 0, 1, "[NAME]",
> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
> +      NULL, "", RO },
> +
>       /* meter commands. */
>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>         NULL, "", RO },
> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>   
>       /* forwarding group commands. */
>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> index f60dde1b6..3d73e9e25 100644
> --- a/utilities/ovn-sbctl.c
> +++ b/utilities/ovn-sbctl.c
> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>   
>       ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>       ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>   
> +    [SBREC_TABLE_MIRROR].row_ids[0]
> +    = {&sbrec_mirror_col_name, NULL, NULL},
> +
>       [SBREC_TABLE_METER].row_ids[0]
>       = {&sbrec_meter_col_name, NULL, NULL},
>
Abhiram R N Nov. 17, 2022, 12:50 p.m. UTC | #9
Hi Ihar,

Thanks for the detailed review of v13. I will address those and together
put the v14 along with the test changes.
Since along with merging the bulk test cases you have asked to expand some
more cases in ovn.at.

The nits, style changes, bulk test updation and other optimization comments
I will address. So I haven't answered inline comments for each of those. So
mostly it's something I will do.

But below I have answered only those where you had concerns. Or where I had
some comments.
Please see inline for those


On Thu, Nov 17, 2022 at 6:37 AM Ihar Hrachyshka <ihrachys@redhat.com> wrote:

> The code is now closer to final, so I've drilled down and listed a number
> of nits and style changes recommended. These are not complete and you
> should apply similar changes to the whole body of the patch.
>
> I reviewed all the code except test suite changes since you were going to
> reshuffle the tests, esp. bulk tests; I will wait for v14 for this. I
> definitely see a number of logical bugs in the code that syncs mirror rules
> to vswitch db (as well as to sbdb).
>
> A general note on test cases: while they seem more complete than before,
> there are more places for improvement: bulk test scenarios will need to
> expand to cover cases where the same port is attached and detached to a
> different number of mirrors in the same transaction; each new field of
> mirror resource (sink, type etc.) should be checked to make sure updates to
> OVN db propagate to SB and finally to vswitchd. You can probably identify
> other lacunae better than me since you wrote the code.
>
> One aspect of the patch that I'm not sure about is I-P engine updates.
> Someone more knowledgeable better review this part, not just me.
>
> Looking forward to see v14 with updated test suite changes.
>
> Thanks,
> Ihar
>
> On 11/4/22 3:09 PM, Abhiram R N wrote:
>
> Mirror creation just creates the mirror. The lsp-attach-mirror
> triggers the sequence to create Mirror in OVS DB on compute node.
> OVS already supports Port Mirroring.
>
> Note: This is targeted to mirror to destinations anywhere outside the
> cluster where the analyser resides and it need not be an OVN node.
>
> Example commands are as below:
>
> Mirror creation
> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>
> Attach a logical port to the mirror.
> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>
> Detach a source from Mirror
> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>
> Mirror deletion
> ovn-nbctl mirror-del mirror1
>
> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com> <vedabarrenkala@gmail.com>
> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com> <vedabarrenkala@gmail.com>
> Signed-off-by: Abhiram R N <abhiramrn@gmail.com> <abhiramrn@gmail.com>
> ---
> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
>              test to make it pass consistently.
> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
>
> V10 --> V11: Addressed review comments from V10 by Ihar
>            i) Expanded bulk updates test cases in ovn.at
>               Overall below cases are covered
>               a) Attaches multiple mirrors (new)
>               b) Equal detaches and attaches (same as V10)
>               c) Detaches more than attaches (new)
>               d) Attaches more than detaches (new)
>               e) Detaches all (new)
>           ii) Addressed the detach all case in mirror.c
>          iii) Minor correction in NEWS
>           iv) Added invalid mirror attach case in ovn-nbctl.at
>
> Files modified (V10 --> V11):
> Code --> mirror.c
> Test --> ovn.at, ovn-nbctl.at
> Misc --> NEWS
>
> Ihar,
>     Regarding mirror_delete function param delete_all it is wrt the
> port binding and if a port binding is removed we delete all its
> attachment. Already that use case is covered in ovn.at.
> Having said that the detaches all had issue in mirror_delete which
> I have addressed. With all the above cases added now in bulk updates
> hope it should give good assurance.
>
>  NEWS                        |   1 +
>  controller/automake.mk      |   4 +-
>  controller/mirror.c         | 538 +++++++++++++++++++++++++
>  controller/mirror.h         |  53 +++
>  controller/ovn-controller.c | 266 ++++++++++--
>  northd/en-northd.c          |   4 +
>  northd/inc-proc-northd.c    |   4 +
>  northd/northd.c             | 172 ++++++++
>  northd/northd.h             |   2 +
>  ovn-nb.ovsschema            |  31 +-
>  ovn-nb.xml                  |  63 +++
>  ovn-sb.ovsschema            |  26 +-
>  ovn-sb.xml                  |  50 +++
>  tests/ovn-nbctl.at          | 120 ++++++
>  tests/ovn-northd.at         | 102 +++++
>  tests/ovn.at                | 778 ++++++++++++++++++++++++++++++++++++
>  utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>  utilities/ovn-sbctl.c       |   4 +
>  18 files changed, 2547 insertions(+), 28 deletions(-)
>  create mode 100644 controller/mirror.c
>  create mode 100644 controller/mirror.h
>
> diff --git a/NEWS b/NEWS
> index 224a7b83e..84b22abdb 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>      any of LR's LRP IP, there is no need to create SNAT entry.  Now such
>      traffic destined to LRP IP is not dropped.
>    - Bump python version required for building OVN to 3.6.
> +  - Added Support for Remote Port Mirroring.
>
>  OVN v22.06.0 - 03 Jun 2022
>  --------------------------
> diff --git a/controller/automake.mk b/controller/automake.mk
> index c2ab1bbe6..334672b4d 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>  	controller/ovsport.h \
>  	controller/ovsport.c \
>  	controller/vif-plug.h \
> -	controller/vif-plug.c
> +	controller/vif-plug.c \
> +	controller/mirror.h \
> +	controller/mirror.c
>
>  controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
>  man_MANS += controller/ovn-controller.8
> diff --git a/controller/mirror.c b/controller/mirror.c
> new file mode 100644
> index 000000000..11f2b63a6
> --- /dev/null
> +++ b/controller/mirror.c
> @@ -0,0 +1,538 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <unistd.h>
> +
> +/* library headers */
> +#include "lib/sset.h"
> +#include "lib/util.h"
> +
> +/* OVS includes. */
> +#include "lib/vswitch-idl.h"
> +#include "openvswitch/vlog.h"
> +
> +/* OVN includes. */
> +#include "binding.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "mirror.h"
> +
> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> +
> +/* Static function declarations */
> +
> +static const struct ovsrec_port *
> +get_port_for_iface(const struct ovsrec_interface *iface,
> +                  const struct ovsrec_bridge *br_int)
> +{
> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *p = br_int->ports[i];
> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> +                return p;
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static bool
> +mirror_create(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    if (pb->n_up && !pb->up[0]) {
> +        return true;
> +    }
> +
> +    if (pb->chassis != pm_ctx->chassis_rec) {
> +        return true;
> +    }
> +
> +    if (!pm_ctx->ovs_idl_txn) {
> +        return false;
> +    }
> +
> +
> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>
> Please remove the log message, it will pollute the log file (please check
> for any other unnecessary log messages)
>
> +    /* Loop through the mirror rules */
> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> +        /* check if the mirror already exists in OVS DB */
> +        bool create_mirror = true;
> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>
> this is log(n^2), should we compute the names set once and then check
> against it?
>
> +                /* Mirror with same name already exists
> +                 * No need to create mirror
> +                 */
> +                create_mirror = false;
> +                break;
> +            }
> +        }
> +
> +        if (create_mirror) {
> +
> +            struct smap options = SMAP_INITIALIZER(&options);
> +            char *port_name, *key;
> +
> +            key = xasprintf("%ld",(long int) pb->mirror_rules[i]->index);
>
> use %d and then you don't need to cast to long int.
>
%d if I use it, it gives a compiler warning as below.
controller/mirror.c:90:31: warning: format ‘%d’ expects argument of type
‘int’, but argument 2 has type ‘int64_t’ {aka ‘long int’} [-Wformat=]
             key = xasprintf("%d", pb->mirror_rules[i]->index);
If I just use %ld and don't do the type casting the normal builds pass well
on my pc. But when I commit the patch 1 or 2 builds fail in the github build
I think mostly the "linux gcc m32..." ones.
Already this I had kept it without typecast before and then had to add type
cast with no other option left since the github tests were not passing
without it.

> +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
> +            smap_add(&options, "key", key);
> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> +                /* Set the ERSPAN index */
> +                smap_add(&options, "erspan_idx", key);
> +                smap_add(&options, "erspan_ver","1");
> +
> +            }
> +            struct ovsrec_interface *iface =
> +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> +            port_name = xasprintf("ovn-%s",
> +                                   pb->mirror_rules[i]->name);
> +
> +            ovsrec_interface_set_name(iface, port_name);
> +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
> +            ovsrec_interface_set_options(iface, &options);
>
> the code calculating the interface options map duplicates what's done in
> check_and_update_interface_table. Consider putting the code to construct
> the options map into a helper function.
>
> +
> +            struct ovsrec_port *port =
> +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_port_set_name(port, port_name);
> +            ovsrec_port_set_interfaces(port, &iface, 1);
> +
> +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> +
> +            smap_destroy(&options);
> +            free(port_name);
> +            free(key);
> +
> +            VLOG_INFO("Creating Mirror in OVS DB");
>
> remove the log message
>
> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> +                                                             mirror);
> +        }
> +
> +        struct local_binding *lbinding = local_binding_find(
> +                               pm_ctx->local_bindings, pb->logical_port);
> +        const struct ovsrec_port *p =
> +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
> +        if (p) {
> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            } else {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            }
> +        }
> +    }
> +    return true;
> +}
> +
> +static void
> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> +                              struct ovsrec_mirror *ovs_mirror)
> +{
> +    char *filter;
> +    if ((ovs_mirror->n_select_dst_port)
> +            && (ovs_mirror->n_select_src_port)) {
> +        filter = "both";
> +    } else if (ovs_mirror->n_select_dst_port) {
> +        filter = "to-lport";
> +    } else {
> +        filter = "from-lport";
> +    }
>
> nit: I would avoid strcmp below and instead introduce a enum for the
> filter type and then switch() over it below. Integer comparisons are
> cheaper than string.
>
> +
> +    if (strcmp(sb_mirror->filter, filter)) {
>
> +        if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"both")) {
>
> add spaces after , - here and below (please fix the whole patch), I think
> these should have been captured by checkpatch script, no?
>
Sure. Got it. Will correct everywhere else. Nope.. checkpatch
didn't complain!

> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"both")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> +                                   struct ovsrec_mirror *ovs_mirror)
>
> please align function parameters here and in other places with spaces
>
> +{
> +    struct smap options = SMAP_INITIALIZER(&options);
> +    char *key, *type;
> +    struct ovsrec_interface *iface =
> +                          ovs_mirror->output_port->interfaces[0];
> +    struct smap *opts = &iface->options;
> +
> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> +    if (erspan_ver) {
> +        type = "erspan";
> +    } else {
> +        type = "gre";
> +    }
> +    if (strcmp(type, sb_mirror->type)) {
> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> +    }
> +
> +    key = xasprintf("%ld",(long int) sb_mirror->index);
>
> Can't you just switch to "%d" and avoid (long int) casting?
>
Nope. For the same reason explained in another one above.

> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> +    smap_add(&options, "key", key);
> +
> +    if (!strcmp(sb_mirror->type, "erspan")) {
> +        /* Set the ERSPAN index */
> +        smap_add(&options, "erspan_idx", key);
> +        smap_add(&options, "erspan_ver","1");
>
> add space before "1"
>
> +    }
> +
> +    ovsrec_interface_set_options(iface, &options);
> +    smap_destroy(&options);
> +    free(key);
> +
>
> remove the empty line
>
> +}
> +
> +static void
> +mirror_update(const struct sbrec_mirror *sb_mirror,
> +              struct ovsrec_mirror *ovs_mirror)
> +{
> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> +
>
> nit: remove the empty line, it doesn't seem to serve any need
>
> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> +}
> +
> +static bool
> +mirror_delete(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx,
> +              struct shash *pb_mirror_map,
> +              bool delete_all)
> +{
> +
> +    if (!pm_ctx->ovs_idl_txn) {
> +        return false;
> +    }
> +
> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> +
> +    if (!delete_all) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> +        }
> +    }
> +
> +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +
> +            struct ovsrec_mirror *ovs_mirror = NULL;
> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> +                                            pb->mirror_rules[i]->name);
> +            if (ovs_mirror) {
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                               ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    struct shash_node *mirror_node;
> +    const struct sbrec_port_binding *sb_pb;
> +    int attach_cnt = 0;
>
> it should be bool since you use it for two states only; also move its
> definition closer to where it's used (under if !sset_find branch)
>
> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> +            /* Find if the mirror has other sources */
> +            attach_cnt = 0;
> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
> +                                       pm_ctx->port_binding_table) {
>
> if I understand it correctly, here for every pb, you walk through the
> whole list of pbs to check if any of them also use the mirror; making this
> operation very expensive. it seems to me that a proper structure like a map
> of mirror-to-(set of port names) should be used to avoid this.
>
> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
> +                                                ovs_mirror->name)) {
> +                        attach_cnt++;
>
> ...and you could probably bail out from here since the only thing you
> really care is that at least one other pb is attached to the mirror
>
> +                    }
> +                }
> +            }
> +            if (attach_cnt) {
> +                /* More than 1 source then just
> +                 * update the mirror table
> +                 */
> +                bool done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
> +                                                   && (done == false)); i++) {
>
> transform done == false condition into if (done) break at the end of the
> for-loop
>
> +                    const struct ovsrec_port *port_rec =
> +                                               ovs_mirror->select_dst_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_dst_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +                done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
> +                                                   && (done == false)); i++) {
> +                    const struct ovsrec_port *port_rec =
> +                                                ovs_mirror->select_src_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_src_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +            } else {
> +                /*
> +                 * If only 1 source delete the output port
> +                 * and then delete the mirror completely
> +                 */
> +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    const char *used_node, *used_next;
> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> +    }
> +    sset_destroy(&pb_mirrors);
> +
> +    return true;
> +}
> +
> +static void
> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> +                            struct port_mirror_ctx *pm_ctx,
> +                            struct shash *pb_mirror_map)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +void
> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> +{
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> +
> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> +}
> +
> +
> +void
> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> +{
> +    shash_init(ovs_mirrors);
> +}
> +
> +void
> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> +                                       pm_ctx->port_binding_table) {
> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> +    }
> +}
> +
> +bool
> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
> +                     struct port_mirror_ctx *pm_ctx)
> +{
> +    bool ret = true;
> +    struct local_binding *lbinding = local_binding_find(
> +                               pm_ctx->local_bindings, pb->logical_port);
> +
> +    if (strcmp(pb->type, "") && (!lbinding)) {
> +        return ret;
> +    }
> +
> +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> +
> +    /* Need to find if mirror needs update */
> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> +    if (!removed) {
> +        if ((pb->n_mirror_rules == 0)
> +              && (shash_is_empty(&port_ovs_mirrors))) {
> +            /* No mirror update */
>
> I think this branch is redundant; the next one will do exactly the same
> for the case of n_mirror_rules = 0
>
> +        } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) {
> +            /* Though number of mirror rules are same,
>
> "Though the number of mirror rules is the same, need to verify the
> contents"
>
> +             * need to verify the contents
> +             */
> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> +                if (!shash_find(&port_ovs_mirrors,
> +                               pb->mirror_rules[i]->name)) {
> +                    /* Mis match in OVN SB DB and OVS DB
> +                     * Delete and Create mirror(s) with proper sources
> +                     */
> +                    ret = mirror_delete(pb, pm_ctx,
> +                                        &port_ovs_mirrors, false);
> +                    if (ret) {
> +                        ret = mirror_create(pb, pm_ctx);
> +                    }
> +                    break;
> +                }
> +            }
> +        } else {
>
> please flatten this out so that there's a single if-else layer for all
> cases
>
> +            /* Update Mirror */
> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> +                /* create mirror,
> +                 * if mirror already exists only update selection
> +                 */
> +                ret = mirror_create(pb, pm_ctx);
>
> I still feel this is incorrect. The fact that pb is attached to a larger
> number of mirrors doesn't necessarily mean that it was *only* attached to
> new mirrors in this transaction, and that it was *not* detached from other
> mirrors in the same transaction. Consider that in the same transaction, a
> port binding was attached to 2 more mirrors AND detached from 1 existing
> mirror; in this case, only mirror_create() will be called, so the old
> attachment won't be cleared.
>

Specifically I tried the below bulk test for the transactions you mentioned
and output was as expected and it passed.
I didn't find any issue here.
Please let me know if this is the case you said.

#Initial state is ls1-lp1 will be attached only to mirror2 and mirror1 and
mirror0 dont have any attachments.

# Attaches THE SAME port to multiple mirrors
# and detach it from existing mirror

check as hv1 ovn-appctl -t ovn-controller debug/pause
check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
check as hv1 ovn-appctl -t ovn-controller debug/resume
check ovn-nbctl --wait=hv sync

as hv1
new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)

AT_CHECK([test "$origA" = "$new1"], [0], [])
AT_CHECK([test "$origA" = "$new2"], [0], [])
OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])

+            } else {
> +                /* delete mirror,
> +                 * if mirror has other sources only update selection
> +                 */
> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
>
> the same problem here: even if the number of mirror attachments reduced,
> it doesn't necessarily mean that the port was not attached to new mirrors
> as part of the transaction.
>
> I've looked through your test cases and I don't see a single case where
> you would detach / attach a different number of mirrors to THE SAME port in
> scope of a single transaction. Please add the test cases to cover this code.
>
Added test as above.  Please see if that is fine.

> +            }
> +        }
> +    } else {
> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>
> I'm puzzled by this branch. So if a pb is removed, then you are going to
> delete all mirrors that were attached to it (delete_all=true) regardless of
> any other ports relying on the mirror. Am I interpreting it correctly? If
> so, why is it the right thing to do? Perhaps adding some more comments in
> this function explaining which branches handle which scenarios would help
> me understand what's happening. Thanks.
>
No. It cannot delete all blindly for the reason you have told.  I feel the
variable name needs correction to detach_all.
If a port binding is deleted then all its attachments are deleted and if
that was the only attachment then it will delete mirror as well and the
output port associated with it.
It's working as expected. Maybe the renaming of the bool variable will
help. And yeah, I will add some more comments.

> +    }
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              &port_ovs_mirrors) {
> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(&port_ovs_mirrors);
> +
> +    return ret;
> +}
> +
> +bool
> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_mirror *mirror = NULL;
> +    struct ovsrec_mirror *ovs_mirror = NULL;
> +
> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
> +    /* For each tracked mirror entry check if OVS entry is there*/
>
> nit: add space before */
>
> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
> +        if (ovs_mirror) {
> +            if (sbrec_mirror_is_deleted(mirror)) {
> +                /* Need to delete the mirror in OVS */
> +                VLOG_INFO("Delete mirror and remove port");
>
> remove this log message, it will pollute log file unnecessarily
>
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                      ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            } else {
> +                mirror_update(mirror, ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +void
> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> +{
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              ovs_mirrors) {
> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(ovs_mirrors);
> +}
> diff --git a/controller/mirror.h b/controller/mirror.h
> new file mode 100644
> index 000000000..85b964f55
> --- /dev/null
> +++ b/controller/mirror.h
> @@ -0,0 +1,53 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_MIRROR_H
> +#define OVN_MIRROR_H 1
> +
> +struct ovsdb_idl_txn;
> +struct ovsrec_port_table;
> +struct ovsrec_bridge;
> +struct ovsrec_bridge_table;
> +struct ovsrec_open_vswitch_table;
> +struct sbrec_chassis;
> +struct ovsrec_interface_table;
> +struct ovsrec_mirror_table;
> +struct sbrec_mirror_table;
> +struct sbrec_port_binding_table;
> +
> +struct port_mirror_ctx {
> +    struct shash *ovs_mirrors;
> +    struct ovsdb_idl_txn *ovs_idl_txn;
> +    const struct ovsrec_port_table *port_table;
> +    const struct ovsrec_bridge *br_int;
> +    const struct sbrec_chassis *chassis_rec;
> +    const struct ovsrec_bridge_table *bridge_table;
> +    const struct ovsrec_open_vswitch_table *ovs_table;
> +    const struct ovsrec_interface_table *iface_table;
> +    const struct ovsrec_mirror_table *mirror_table;
> +    const struct sbrec_mirror_table *sb_mirror_table;
> +    const struct sbrec_port_binding_table *port_binding_table;
> +    struct shash *local_bindings;
> +};
> +
> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> +void ovn_port_mirror_init(struct shash *);
> +void ovn_port_mirror_destroy(struct shash *);
> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> +                                  bool removed,
> +                                  struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> +#endif
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 8895c7a2b..15ab17c4a 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -78,6 +78,7 @@
>  #include "lib/inc-proc-eng.h"
>  #include "lib/ovn-l7.h"
>  #include "hmapx.h"
> +#include "mirror.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
> +    mirror_register_ovs_idl(ovs_idl);
>  }
>
>  #define SB_NODES \
> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      SB_NODE(load_balancer, "load_balancer") \
>      SB_NODE(fdb, "fdb") \
>      SB_NODE(meter, "meter") \
> +    SB_NODE(mirror, "mirror") \
>      SB_NODE(static_mac_binding, "static_mac_binding")
>
>  enum sb_engine_node {
> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>      OVS_NODE(bridge, "bridge") \
>      OVS_NODE(port, "port") \
>      OVS_NODE(interface, "interface") \
> -    OVS_NODE(qos, "qos")
> +    OVS_NODE(qos, "qos") \
> +    OVS_NODE(mirror, "mirror")
>
>  enum ovs_engine_node {
>  #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>      free(lbs);
>  }
>
> +/* Mirror Engine */
> +struct ed_type_port_mirror {
> +    struct shash ovs_mirrors;
> +};
> +
> +static void *
> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> +                    struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> +    return port_mirror;
> +}
> +
> +static void
> +en_port_mirror_cleanup(void *data)
> +{
> +    struct ed_type_port_mirror *port_mirror = data;
> +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> +}
> +
> +static void
> +init_port_mirror_ctx(struct engine_node *node,
> +                 struct ed_type_runtime_data *rt_data,
> +                 struct ed_type_port_mirror *port_mirror_data,
> +                 struct port_mirror_ctx *pm_ctx)
> +{
> +    struct ovsrec_open_vswitch_table *ovs_table =
> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_open_vswitch", node));
> +    struct ovsrec_bridge_table *bridge_table =
> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_bridge", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
> +
> +    ovs_assert(br_int && chassis_id);
> +    const struct sbrec_chassis *chassis = NULL;
> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_chassis", node),
> +                "name");
> +
> +    if (chassis_id) {
> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> +    }
> +    ovs_assert(chassis);
> +
> +    struct ovsrec_port_table *port_table =
> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_port", node));
> +
> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> +        engine_get_input_data("ovs_interface_shadow", node);
> +
> +    struct ovsrec_mirror_table *mirror_table =
> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
> +            engine_get_input("OVS_mirror", node));
> +
> +    struct sbrec_port_binding_table *pb_table =
> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
> +            engine_get_input("SB_port_binding", node));
> +
> +    struct sbrec_mirror_table *sb_mirror_table =
> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
> +            engine_get_input("SB_mirror", node));
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                           &port_mirror_data->ovs_mirrors) {
> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> +    }
> +
> +    const struct ovsrec_mirror *ovsmirror = NULL;
> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
> +    }
> +
> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> +    pm_ctx->port_table = port_table;
> +    pm_ctx->iface_table = iface_shadow->iface_table;
> +    pm_ctx->mirror_table = mirror_table;
> +    pm_ctx->port_binding_table = pb_table;
> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> +    pm_ctx->br_int = br_int;
> +    pm_ctx->chassis_rec = chassis;
> +    pm_ctx->bridge_table = bridge_table;
> +    pm_ctx->ovs_table = ovs_table;
> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> +}
> +
> +static void
> +en_port_mirror_run(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    ovn_port_mirror_run(&pm_ctx);
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +static bool
> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    /* There is no tracked data. Fall back to full recompute of port_mirror */
> +    if (!rt_data->tracked) {
> +        return false;
> +    }
> +
> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> +    if (hmap_is_empty(tracked_dp_bindings)) {
> +        return true;
> +    }
> +
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    struct tracked_datapath *tdp;
> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> +            /* Fall back to full recompute when a local datapath
> +             * is added or deleted. */
> +            return false;
> +        }
> +
> +        struct shash_node *shash_node;
> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> +            struct tracked_lport *lport = shash_node->data;
> +            bool removed =
> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
> +static bool
> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle port binding updates (i.,e when the mirror column
>
> "i.e."
>
> +     * of port_binding is updated)
> +     */
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +                                               pm_ctx.port_binding_table) {
> +        bool removed = sbrec_port_binding_is_deleted(pb);
> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> +            return false;
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
> +static bool
> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle sb mirror updates
> +     */
>
> nit: combine two lines above
>
> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> +        return false;
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
>  /* Engine node which is used to handle the Non VIF data like
>   *   - OVS patch ports
>   *   - Tunnel ports and the related chassis information.
> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>      ENGINE_NODE(northd_options, "northd_options");
>      ENGINE_NODE(dhcp_options, "dhcp_options");
> +    ENGINE_NODE(port_mirror, "port_mirror");
>
>  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>      SB_NODES
> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>      engine_add_input(&en_flow_output, &en_pflow_output,
>                       flow_output_pflow_output_handler);
>
> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> +                     engine_noop_handler);
> +    engine_add_input(&en_flow_output, &en_port_mirror,
> +                     engine_noop_handler);
>
> perhaps I am missing something, but why would flow_output depend on mirror
> updates, when it seems like the only action needed is mapping ovn mirror
> definitions to ovs mirror table?
>
One thing for sure is, without this it wont work properly.
Along with port binding changes we handle runtime data changes as well. And
to get that call
this noop_handler for en_flow_output was necessary else I wasn't getting
those calls. (Maybe someone who knows
Engine working well can comment on why. I am not sure.)
This was probably the first change when I added a new engine node as
suggested by Numan.
To reconfirm I just commented above and tried running the Mirror test and
it fails in ovn.at

> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> +                     port_mirror_runtime_data_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> +                     port_mirror_sb_mirror_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> +                     port_mirror_port_binding_handler);
> +
>      struct engine_arg engine_arg = {
>          .sb_idl = ovnsb_idl_loop.idl,
>          .ovs_idl = ovs_idl_loop.idl,
> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>
>                      stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>                                      time_msec());
> -                    if (ovnsb_idl_txn) {
> -                        if (ofctrl_has_backlog()) {
> -                            /* When there are in-flight messages pending to
> -                             * ovs-vswitchd, we should hold on recomputing so
> -                             * that the previous flow installations won't be
> -                             * delayed.  However, we still want to try if
> -                             * recompute is not needed and we can quickly
> -                             * incrementally process the new changes, to avoid
> -                             * unnecessarily forced recomputes later on.  This
> -                             * is because the OVSDB change tracker cannot
> -                             * preserve tracked changes across iterations.  If
> -                             * change tracking is improved, we can simply skip
> -                             * this round of engine_run and continue processing
> -                             * acculated changes incrementally later when
> -                             * ofctrl_has_backlog() returns false. */
> -                            engine_run(false);
> -                        } else {
> -                            engine_run(true);
> -                        }
> -                    } else {
> -                        /* Even if there's no SB DB transaction available,
> +
> +                    bool allow_engine_recompute = true;
> +
> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
> +                                                     ofctrl_has_backlog()) {
> +                        /* When there are in-flight messages pending to
> +                         * ovs-vswitchd, we should hold on recomputing so
> +                         * that the previous flow installations won't be
> +                         * delayed.  However, we still want to try if
> +                         * recompute is not needed and we can quickly
> +                         * incrementally process the new changes, to avoid
> +                         * unnecessarily forced recomputes later on.  This
> +                         * is because the OVSDB change tracker cannot
> +                         * preserve tracked changes across iterations.  If
> +                         * change tracking is improved, we can simply skip
> +                         * this round of engine_run and continue processing
> +                         * acculated changes incrementally later when
> +                         * ofctrl_has_backlog() returns false. */
> +
> +                        /* Even if there's no SB/OVS DB transaction available,
>                           * try to run the engine so that we can handle any
>                           * incremental changes that don't require a recompute.
>                           * If a recompute is required, the engine will abort,
>                           * triggerring a full run in the next iteration.
>                           */
> -                        engine_run(false);
> +                        allow_engine_recompute = false;
>                      }
> +
> +                    engine_run(allow_engine_recompute);
> +
>                      stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>                                     time_msec());
>                      if (engine_has_updated()) {
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 7fe83db64..608220b1f 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("NB_acl", node));
>      input_data.nbrec_static_mac_binding_table =
>          EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> +    input_data.nbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>
>      input_data.sbrec_sb_global_table =
>          EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>      input_data.sbrec_static_mac_binding_table =
>          EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> +    input_data.sbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>
>      northd_run(&input_data, data,
>                 eng_ctx->ovnnb_idl_txn,
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 54e0ad3b0..ac27a730e 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      NB_NODE(acl, "acl") \
>      NB_NODE(logical_router, "logical_router") \
>      NB_NODE(qos, "qos") \
> +    NB_NODE(mirror, "mirror") \
>      NB_NODE(meter, "meter") \
>      NB_NODE(meter_band, "meter_band") \
>      NB_NODE(logical_router_port, "logical_router_port") \
> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      SB_NODE(logical_flow, "logical_flow") \
>      SB_NODE(logical_dp_group, "logical_DP_group") \
>      SB_NODE(multicast_group, "multicast_group") \
> +    SB_NODE(mirror, "mirror") \
>      SB_NODE(meter, "meter") \
>      SB_NODE(meter_band, "meter_band") \
>      SB_NODE(datapath_binding, "datapath_binding") \
> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_nb_acl, NULL);
>      engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>      engine_add_input(&en_northd, &en_nb_qos, NULL);
> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>      engine_add_input(&en_northd, &en_nb_meter, NULL);
>      engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>      engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_sb_address_set, NULL);
>      engine_add_input(&en_northd, &en_sb_port_group, NULL);
>      engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>      engine_add_input(&en_northd, &en_sb_meter, NULL);
>      engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>      engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> diff --git a/northd/northd.c b/northd/northd.c
> index b7388afc5..52abdda28 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>      free(requested_chassis_sb);
>  }
>
> +static void
> +do_sb_mirror_addition(struct northd_input *input_data,
> +                      const struct ovn_port *op)
> +{
> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +        const struct sbrec_mirror *sb_mirror;
> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> +                                     input_data->sbrec_mirror_table) {
> +            if (!strcmp(sb_mirror->name,
> +                        op->nbsp->mirror_rules[i]->name)) {
> +                /* Add the value to SB */
> +                sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> +                                                                sb_mirror);
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
> +                                       const struct ovn_port *op)
> +{
> +    size_t i = 0;
> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> +        /* Needs deletion in SB */
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
>
> this will only delete old mirror rules; but it won't add any new rules
>
But new rules get added in the else case right?
From my understanding we get a port binding update for every change and
that's what I had noticed during my testing
I used to get separate SB port binding update calls for each port binding
change.

So, at any point of time for a particular port binding it can be in 3
states when some change is triggered
a) SB has more mirror rules and NB has less
b) NB has more mirror rules and SB has less
c) Both have the same number of mirror rules

All 3 cases are handled and I don't see an issue here. If it had then we
would have shown problems in the
bulk tests right?

To double check I tried below adding below bulk test. And don't see any
issue.
Initially I have ls1-lp1 attached to mirror0, mirror1 and mirror3
And nothing attached to mirror2..
So the pb will have 3 mirror rules.
Next as a bulk update I detach 2 (mirror0 and mirror3) and attach 1
different mirror mirror2.
Note mirror1 attachment still remains as its  unchanged

So the pb will have 2 mirror rules now.. (mirror1 and mirror2)
And this is working fine. I can add this as well to the bulk test maybe.
(Although we have covered 'more detach and less attach' before this it was
NOT with the same port binding.
  The above one I added in this reply is with THE SAME port binding but it
is 'more attach and less detach'
So, in that way it might be a different case)
And it is working fine. So hope that clears your concerns regarding this
part of the code.

check as hv1 ovn-appctl -t ovn-controller debug/pause
check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
check as hv1 ovn-appctl -t ovn-controller debug/resume

check ovn-nbctl --wait=hv sync

as hv1
new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)

AT_CHECK([test "$origA" = "$new1"], [0], [])

AT_CHECK([test "$origA" = "$new2"], [0], [])

OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])

Thanks  & Regards,
Abhiram R N

> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
> +        /* Needs addition in SB */
> +        do_sb_mirror_addition(input_data, op);
>
> vice versa here: it will only add new rules but not remove obsolete rules
>
> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
> +        /*
> +         * Check if its the same mirrors on both SB and NB DBs
> +         * If not update accordingly.
> +         */
> +        bool needs_sb_addition = false;
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
> +                    needs_sb_addition = true;
> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +        if (needs_sb_addition) {
> +            do_sb_mirror_addition(input_data, op);
> +        }
> +    }
> +}
> +
>  static void
>  ovn_port_update_sbrec(struct northd_input *input_data,
>                        struct ovsdb_idl_txn *ovnsb_txn,
> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>          }
>          sbrec_port_binding_set_external_ids(op->sb, &ids);
>          smap_destroy(&ids);
> +
> +        if (!op->nbsp->n_mirror_rules) {
> +            /* Nothing is set. Clear mirror_rules from pb. */
> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> +        } else {
> +            /* Check if SB DB update needed */
> +            sbrec_port_binding_update_mirror_rules(input_data, op);
> +        }
> +
>      }
>      if (op->tunnel_key != op->sb->tunnel_key) {
>          sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>      shash_destroy(&sb_meters);
>  }
>
> +static bool
> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> +                  const struct sbrec_mirror *sb_mirror)
> +{
> +
> +    if (nb_mirror->index != sb_mirror->index) {
> +        return true;
> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static void
> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> +                             const char *mirror_name,
> +                             const struct nbrec_mirror *nb_mirror,
> +                             struct shash *sb_mirrors,
> +                             struct sset *used_sb_mirrors)
> +{
> +    const struct sbrec_mirror *sb_mirror;
> +    bool new_sb_mirror = false;
> +
> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> +    if (!sb_mirror) {
> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> +        new_sb_mirror = true;
> +    }
> +    sset_add(used_sb_mirrors, mirror_name);
> +
> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
> +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> +    }
> +}
> +
> +static void
> +sync_mirrors(struct northd_input *input_data,
> +            struct ovsdb_idl_txn *ovnsb_txn)
> +{
> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> +
> +    const struct sbrec_mirror *sb_mirror;
> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> +    }
> +
> +    const struct nbrec_mirror *nb_mirror;
> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
> +                                     &sb_mirrors, &used_sb_mirrors);
> +    }
> +
> +    const char *used_mirror;
> +    const char *used_mirror_next;
> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
> +        shash_find_and_delete(&sb_mirrors, used_mirror);
> +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
> +    }
> +    sset_destroy(&used_sb_mirrors);
> +
> +    struct shash_node *node, *next;
> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> +        sbrec_mirror_delete(node->data);
> +        shash_delete(&sb_mirrors, node);
> +    }
> +    shash_destroy(&sb_mirrors);
> +}
> +
>  /*
>   * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>   * and Southbound db.
> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>      sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>      sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>      sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> +    sync_mirrors(input_data, ovnsb_txn);
>      sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>      cleanup_stale_fdb_entries(input_data, &data->datapaths);
>      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> diff --git a/northd/northd.h b/northd/northd.h
> index da90e2815..17a62ea10 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -36,6 +36,7 @@ struct northd_input {
>      const struct nbrec_acl_table *nbrec_acl_table;
>      const struct nbrec_static_mac_binding_table
>          *nbrec_static_mac_binding_table;
> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>
>      /* Southbound table references */
>      const struct sbrec_sb_global_table *sbrec_sb_global_table;
> @@ -55,6 +56,7 @@ struct northd_input {
>      const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>      const struct sbrec_static_mac_binding_table
>          *sbrec_static_mac_binding_table;
> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>
>      /* Indexes */
>      struct ovsdb_idl_index *sbrec_chassis_by_name;
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 174364c8b..01de55222 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "6.3.0",
> -    "cksum": "4042813038 31869",
> +    "version": "6.4.0",
> +    "cksum": "589874483 33352",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -132,6 +132,11 @@
>                                              "refType": "weak"},
>                                   "min": 0,
>                                   "max": 1}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "ha_chassis_group": {
>                      "type": {"key": {"type": "uuid",
>                                       "refTable": "HA_Chassis_Group",
> @@ -301,6 +306,28 @@
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
>              "isRoot": false},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["from-lport",
> +                                                             "to-lport",
> +                                                             "both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["gre",
> +                                                             "erspan"]]}}},
> +                "index": {"type": "integer"},
> +                "src": {"type": {"key": {"type": "uuid",
> +                                           "refTable": "Logical_Switch_Port",
> +                                           "refType": "weak"},
> +                                   "min": 0,
> +                                   "max": "unlimited"}},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Meter": {
>              "columns": {
>                  "name": {"type": "string"},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index f41e9d7c0..d8730c8fc 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1554,6 +1554,11 @@
>        </column>
>      </group>
>
> +    <column name="mirror_rules">
> +        Mirror rules that apply to logical switch port which is the source.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>      <column name="ha_chassis_group">
>        References a row in the OVN Northbound database's
>        <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> @@ -2491,6 +2496,64 @@
>      </column>
>    </table>
>
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
> +      the <ref table="Logical_Switch_Port"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.
> +        Supported values for filter to-lport / from-lport / both
> +        to-lport - to mirror packets coming into logical port
> +        from-lport - to mirror packets going out of logical port
> +        both - to mirror packets coming into and going out of logical port.
> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.
> +        The value it takes is an IP address of the sink port.
> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets. Supported Tunnel types gre and erspan
> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the tunnel ID. Depending on the
> +        tunnel type configured, GRE key value if type GRE and erspan_idx value
> +        if ERSPAN
> +      </p>
> +    </column>
> +
> +    <column name="src">
> +      <p>
> +        The value of this field represents a list of source ports for the
> +        mirror. Please see the <ref table="Logical_Switch_Port"/> table.
> +      </p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
> +
>    <table name="Meter" title="Meter entry">
>      <p>
>        Each row in this table represents a meter that can be used for QoS or
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 576ebbdeb..b83134416 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.25.0",
> -    "cksum": "53184112 28845",
> +    "version": "20.26.0",
> +    "cksum": "2344151793 30004",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -142,6 +142,23 @@
>              "indexes": [["datapath", "tunnel_key"],
>                          ["datapath", "name"]],
>              "isRoot": true},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["from-lport",
> +                                                      "to-lport","both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["gre", "erspan"]]}}},
> +                "index": {"type": "integer"},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Meter": {
>              "columns": {
>                  "name": {"type": "string"},
> @@ -230,6 +247,11 @@
>                                                        "refTable": "Encap",
>                                                        "refType": "weak"},
>                                      "min": 0, "max": "unlimited"}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "mac": {"type": {"key": "string",
>                                   "min": 0,
>                                   "max": "unlimited"}},
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 315d60853..05c0db6b4 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>      </column>
>    </table>
>
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Port_Binding"/> column in
> +      the <ref table="Port_Binding"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets
> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the key/idx depending on the
> +        tunnel type configured
> +      </p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
> +
>    <table name="Meter" title="Meter entry">
>      <p>
>        Each row in this table represents a meter that can be used for QoS or
> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>        </column>
>      </group>
>
> +    <column name="mirror_rules">
> +        Mirror rules that apply to the port binding.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>      <group title="Patch Options">
>        <p>
>          These options apply to logical ports with <ref column="type"/> of
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 4d480e357..d79f9d929 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>
>  dnl ---------------------------------------------------------------------
>
> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> +AT_CHECK([ovn-nbctl ls-add sw0])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> +
> +dnl Add duplicate mirror name
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> +
> +dnl Attach invalid source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> +
> +dnl Attach source port to invalid mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], [stderr])
> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
> +
> +dnl Attach source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> +
> +dnl Attach one more source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> +
> +dnl Verify if multiple ports are attached to the same mirror properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +mirror3:
> +  Type     :  gre
> +  Sink     :  10.10.10.3
> +  Filter   :  to-lport
> +  Index/Key:  2
> +  Sources  :  sw0-port1  sw0-port3
> +])
> +
> +dnl Detach one source port from mirror
> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> +
> +dnl Verify if detach source port from mirror happens properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +mirror3:
> +  Type     :  gre
> +  Sink     :  10.10.10.3
> +  Filter   :  to-lport
> +  Index/Key:  2
> +  Sources  :  sw0-port1
> +])
> +
> +dnl Delete a single mirror which has source attached.
> +AT_CHECK([ovn-nbctl mirror-del mirror3])
> +
> +dnl Check if the detach happened from source properly
> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules |  cut -b 3], [0], [dnl
> +
> +])
> +
> +dnl Check if the mirror deleted properly
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +])
> +
> +dnl Delete another mirror
> +AT_CHECK([ovn-nbctl mirror-del mirror2])
> +
> +dnl Update the Sink address
> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> +
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  192.168.1.13
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +])
> +
> +dnl Delete all mirrors
> +AT_CHECK([ovn-nbctl mirror-del])
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +])])
> +
> +dnl ---------------------------------------------------------------------
> +
>  OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>  AT_CHECK([ovn-nbctl lr-add lr0])
>  AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 4f399eccb..4e6c268e4 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Check NB-SB mirrors sync])
> +AT_KEYWORDS([mirrors])
> +ovn_start
> +
> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"10.10.10.2"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +erspan
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . sink=192.168.1.13
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +erspan
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . type=gre
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . index=12
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +12
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . filter=to-lport
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +to-lport
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +12
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD_NO_HV([
>  AT_SETUP([ACL skip hints for stateless config])
>  AT_KEYWORDS([acl])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index f8b8db4df..cd5527ea1 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror])
> +AT_KEYWORDS([Mirror])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +ovn-nbctl dump-flows > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +
> +for i in 1 2; do
> +    : > vif$i.expected
> +done
> +
> +net_add n2
> +
> +sim_add hv2
> +as hv2
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> +ovn_attach n2 br-phys 192.168.1.12
> +
> +OVN_POPULATE_ARP
> +
> +as hv1
> +
> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> +#
> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> +# provided, then it should be the ip and icmp checksums of the packet
> +# responded; otherwise, no reply is expected.
> +# In the absence of an ip checksum calculation helpers, this relies
> +# on the caller to provide the checksums for the ip and icmp headers.
> +# XXX This should be more systematic.
> +#
> +# INPORT is an lport number, e.g. 11 for vif11.
> +# ETH_SRC and ETH_DST are each 12 hex digits.
> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> +# ENCAP_TYPE - gre or erspan
> +# FILTER - Mirror Filter - to-lport / from-lport
> +test_ipv4_icmp_request() {
> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
> +    shift; shift; shift; shift; shift; shift; shift
> +    shift; shift; shift; shift;
> +
> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> +    local ip_ttl=02
> +    local icmp_id=5fbf
> +    local icmp_seq=0001
> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> +    local icmp_type_code_request=0800
> +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> +
> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> +
> +    # Expect to receive the reply, if any. In same port where packet was sent.
> +    # Note: src and dst fields are expected to be reversed.
> +    local icmp_type_code_response=0000
> +    local reply_icmp_ttl=fe
> +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> +    echo $reply >> vif$inport.expected
> +    local remote_mac=000000020200
> +    local local_mac=000000010200
> +    if test ${mirror_encap_type} = "gre" ; then
> +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> +        fi
> +    elif test ${mirror_encap_type} = "erspan" ; then
> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> +        local erspan_seq0=100088be000000001000000000000000
> +        local erspan_seq1=100088be000000011000000000000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> +        fi
> +    fi
> +    echo $mirror >> br-phys_n1.expected
> +
> +}
> +
> +# Set IPs
> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> +l1_ip=$(ip_to_hex 192 168 1 2)
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=erspan
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . filter=from-lport
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=gre
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +echo "---------OVN NB Mirror-----"
> +ovn-nbctl mirror-list
> +
> +echo "---------OVS Mirror----"
> +ovs-vsctl list Mirror
> +
> +echo "-----------------------"
> +
> +echo "Verifying Mirror deletion in OVS"
> +# Set vif1 iface-id such that OVN releases port binding
> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> +])
> +
> +# Set vif1 iface-id back to ls1-lp1
> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
> +
> +# Delete vif1 so that OVN releases port binding
> +check ovs-vsctl del-port br-int vif1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk swap attachments])
> +AT_KEYWORDS([Mirror test bulk swap attachments])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +# Equal detaches and attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk attach multiple])
> +AT_KEYWORDS([Mirror test bulk attach multiple])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +
> +# Attaches multiple mirrors
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk more detach and less attach])
> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl --wait=hv sync
> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl --wait=hv sync
> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +
> +check ovn-nbctl --wait=hv sync
> +
> +# Detaches more than attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$origB" = "$new2"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk attach more than detach])
> +AT_KEYWORDS([Mirror test bulk attach more than detach])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check ovn-nbctl --wait=hv sync
> +
> +# Attaches more than detaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk detach multiple])
> +AT_KEYWORDS([Mirror test bulk detach multiple])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl <http://192.168.1.1/24+ovn-nbctl> lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl <http://172.16.1.1/24+ovn-nbctl> lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +# Detaches all
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
>
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([Port Groups])
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 811468dc6..af2e61435 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -271,6 +271,19 @@ QoS commands:\n\
>                              remove QoS rules from SWITCH\n\
>    qos-list SWITCH           print QoS rules for SWITCH\n\
>  \n\
> +Mirror commands:\n\
> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> +                            add a mirror with given name\n\
> +                            specify TYPE 'gre' or 'erspan'\n\
> +                            specify the tunnel INDEX value\n\
> +                                (indicates key if GRE\n\
> +                                 erpsan_idx if ERSPAN)\n\
> +                            specify FILTER for mirroring selection\n\
> +                                'to-lport' / 'from-lport' / 'both'\n\
> +                            specify Sink / Destination i.e. Remote IP\n\
> +  mirror-del [NAME]         remove mirrors\n\
> +  mirror-list               print mirrors\n\
> +\n\
>  Meter commands:\n\
>    [--fair]\n\
>    meter-add NAME ACTION RATE UNIT [BURST]\n\
> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>                              set dhcpv6 options for PORT\n\
>    lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>    lsp-get-ls PORT           get the logical switch which the port belongs to\n\
> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>  \n\
>  Forwarding group commands:\n\
>    [--liveness]\n\
> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>  }
>
> +static void
> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_switch_port_col_mirror_rules);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static int
> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> +{
> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> +
> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> +
> +    return strcmp(mirror1->name,mirror2->name);
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> +                    bool must_exist,
> +                    const struct nbrec_mirror **mirror_p)
> +{
> +    const struct nbrec_mirror *mirror = NULL;
> +    *mirror_p = NULL;
> +
> +    struct uuid mirror_uuid;
> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> +    if (is_uuid) {
> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> +    }
> +
> +    if (!mirror) {
> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +            if (!strcmp(mirror->name, id)) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    if (!mirror && must_exist) {
> +        return xasprintf("%s: mirror %s not found",
> +                         id, is_uuid ? "UUID" : "name");
> +    }
> +
> +    *mirror_p = mirror;
> +    return NULL;
> +}
> +
> +static void
> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Check if same mirror rule already exists for the lsp */
> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +            if (!may_exist) {
> +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
> +                          ctx->argv[1]);
> +                return;
> +            }
> +            return;
> +        }
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> +
> +}
> +
> +static void
> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> +
> +}
> +
>  static void
>  nbctl_lsp_set_type(struct ctl_context *ctx)
>  {
> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>      nbrec_ha_chassis_set_priority(ha_chassis, priority);
>  }
>
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_filter(const char *arg, const char **selection_p)
> +{
> +    /* Validate selection.  Only require the first letter. */
> +    if (arg[0] == 't') {
> +        *selection_p = "to-lport";
> +    } else if (arg[0] == 'f') {
> +        *selection_p = "from-lport";
> +    } else if (arg[0] == 'b') {
> +        *selection_p = "both";
> +    } else {
> +        *selection_p = NULL;
> +        return xasprintf("%s: selection must be \"to-lport\" or "
> +                         "\"from-lport\" or \"both\" ", arg);
> +    }
> +    return NULL;
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_type(const char *arg, const char **type_p)
> +{
> +    /* Validate type.  Only require the first letter. */
> +    if (arg[0] == 'g') {
> +        *type_p = "gre";
> +    } else if (arg[0] == 'e') {
> +        *type_p = "erspan";
> +    } else {
> +        *type_p = NULL;
> +        return xasprintf("%s: type must be \"gre\" or "
> +                         "\"erspan\"", arg);
> +    }
> +    return NULL;
> +}
> +
> +static void
> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +}
> +
> +static void
> +nbctl_mirror_add(struct ctl_context *ctx)
> +{
> +    const char *filter = NULL;
> +    const char *sink_ip = NULL;
> +    const char *type = NULL;
> +    const char *name = NULL;
> +    char *new_sink_ip = NULL;
> +    int64_t index;
> +    char *error = NULL;
> +    const struct nbrec_mirror *mirror_check = NULL;
> +
> +    /* Mirror Name */
> +    name = ctx->argv[1];
> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> +        if (!strcmp(mirror_check->name, name)) {
> +            ctl_error(ctx, "Mirror with %s name already exists.",
> +                      name);
> +            return;
> +        }
> +    }
> +
> +    /* Tunnel Type - GRE/ERSPAN */
> +    error = parse_type(ctx->argv[2], &type);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* tunnel index / GRE key / ERSPAN idx */
> +    index = atoi(ctx->argv[3]);
> +
> +    /* Filter for mirroring */
> +    error = parse_filter(ctx->argv[4], &filter);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Destination / Sink details */
> +    sink_ip = ctx->argv[5];
> +
> +    /* check if it is a valid ip */
> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
> +    if (!new_sink_ip) {
> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
> +    }
> +
> +    if (new_sink_ip) {
> +        free(new_sink_ip);
> +    } else {
> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> +        return;
> +    }
> +
> +    /* Create the mirror. */
> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> +    nbrec_mirror_set_name(mirror, name);
> +    nbrec_mirror_set_index(mirror, index);
> +    nbrec_mirror_set_filter(mirror, filter);
> +    nbrec_mirror_set_type(mirror, type);
> +    nbrec_mirror_set_sink(mirror, sink_ip);
> +
> +}
> +
> +static void
> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_mirror *mirror, *next;
> +
> +    /* If a name is not specified, delete all mirrors. */
> +    if (ctx->argc == 1) {
> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> +            nbrec_mirror_delete(mirror);
> +        }
> +        return;
> +    }
> +
> +    /* Remove the matching mirror. */
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (strcmp(ctx->argv[1], mirror->name)) {
> +            continue;
> +        }
> +        nbrec_mirror_delete(mirror);
> +        return;
> +    }
> +}
> +
> +static void
> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_list(struct ctl_context *ctx)
> +{
> +
> +    const struct nbrec_mirror **mirrors = NULL;
> +    const struct nbrec_mirror *mirror;
> +    size_t n_capacity = 0;
> +    size_t n_mirrors = 0;
> +
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (n_mirrors == n_capacity) {
> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
> +        }
> +
> +        mirrors[n_mirrors] = mirror;
> +        n_mirrors++;
> +    }
> +
> +    if (n_mirrors) {
> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> +    }
> +
> +    for (size_t i = 0; i < n_mirrors; i++) {
> +        mirror = mirrors[i];
> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> +        /* print all the values */
> +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
> +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
> +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
> +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
> +                                                (long int) mirror->index);
> +        ds_put_cstr(&ctx->output,   "  Sources  :");
> +        if (mirror->n_src > 0) {
> +            struct svec srcs;
> +            const char *src;
> +            size_t j;
> +            svec_init(&srcs);
> +            for (j = 0; j < mirror->n_src; j++) {
> +                svec_add(&srcs, mirror->src[j]->name);
> +            }
> +            svec_sort(&srcs);
> +            SVEC_FOR_EACH (j, src, &srcs) {
> +                ds_put_format(&ctx->output, "  %s", src);
> +            }
> +            svec_destroy(&srcs);
> +        } else {
> +            ds_put_cstr(&ctx->output, "  None attached");
> +        }
> +        ds_put_cstr(&ctx->output, "\n");
> +    }
> +
> +    free(mirrors);
> +}
> +
>  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>      [NBREC_TABLE_DHCP_OPTIONS].row_ids
>      = {{&nbrec_logical_switch_port_col_name, NULL,
> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>      { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>        NULL, "", RO },
>
> +    /* mirror commands. */
> +    { "mirror-add", 5, 5,
> +      "NAME TYPE INDEX FILTER IP",
> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
> +    { "mirror-del", 0, 1, "[NAME]",
> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
> +      NULL, "", RO },
> +
>      /* meter commands. */
>      { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>        nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>        nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>      { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>        NULL, "", RO },
> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>
>      /* forwarding group commands. */
>      { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> index f60dde1b6..3d73e9e25 100644
> --- a/utilities/ovn-sbctl.c
> +++ b/utilities/ovn-sbctl.c
> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>
>      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>      [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>      = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>
> +    [SBREC_TABLE_MIRROR].row_ids[0]
> +    = {&sbrec_mirror_col_name, NULL, NULL},
> +
>      [SBREC_TABLE_METER].row_ids[0]
>      = {&sbrec_meter_col_name, NULL, NULL},
>
>
>
Ihar Hrachyshka Nov. 17, 2022, 7:08 p.m. UTC | #10
On 11/17/22 7:50 AM, Abhiram R N wrote:
> Hi Ihar,
>
> Thanks for the detailed review of v13. I will address those and 
> together put the v14 along with the test changes.
> Since along with merging the bulk test cases you have asked to expand 
> some more cases in ovn.at <http://ovn.at>.
>
> The nits, style changes, bulk test updation and other optimization 
> comments I will address. So I haven't answered inline comments for 
> each of those. So mostly it's something I will do.
>
> But below I have answered only those where you had concerns. Or where 
> I had some comments.
> Please see inline for those
>
>
> On Thu, Nov 17, 2022 at 6:37 AM Ihar Hrachyshka <ihrachys@redhat.com> 
> wrote:
>
>     The code is now closer to final, so I've drilled down and listed a
>     number of nits and style changes recommended. These are not
>     complete and you should apply similar changes to the whole body of
>     the patch.
>
>     I reviewed all the code except test suite changes since you were
>     going to reshuffle the tests, esp. bulk tests; I will wait for v14
>     for this. I definitely see a number of logical bugs in the code
>     that syncs mirror rules to vswitch db (as well as to sbdb).
>
>     A general note on test cases: while they seem more complete than
>     before, there are more places for improvement: bulk test scenarios
>     will need to expand to cover cases where the same port is attached
>     and detached to a different number of mirrors in the same
>     transaction; each new field of mirror resource (sink, type etc.)
>     should be checked to make sure updates to OVN db propagate to SB
>     and finally to vswitchd. You can probably identify other**lacunae
>     better than me since you wrote the code.
>
>     One aspect of the patch that I'm not sure about is I-P engine
>     updates. Someone more knowledgeable better review this part, not
>     just me.
>
>     Looking forward to see v14 with updated test suite changes.
>
>     Thanks,
>     Ihar
>
>     On 11/4/22 3:09 PM, Abhiram R N wrote:
>>     Mirror creation just creates the mirror. The lsp-attach-mirror
>>     triggers the sequence to create Mirror in OVS DB on compute node.
>>     OVS already supports Port Mirroring.
>>
>>     Note: This is targeted to mirror to destinations anywhere outside the
>>     cluster where the analyser resides and it need not be an OVN node.
>>
>>     Example commands are as below:
>>
>>     Mirror creation
>>     ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>>
>>     Attach a logical port to the mirror.
>>     ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>>
>>     Detach a source from Mirror
>>     ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>>
>>     Mirror deletion
>>     ovn-nbctl mirror-del mirror1
>>
>>     Co-authored-by: Veda Barrenkala<vedabarrenkala@gmail.com>  <mailto:vedabarrenkala@gmail.com>
>>     Signed-off-by: Veda Barrenkala<vedabarrenkala@gmail.com>  <mailto:vedabarrenkala@gmail.com>
>>     Signed-off-by: Abhiram R N<abhiramrn@gmail.com>  <mailto:abhiramrn@gmail.com>
>>     ---
>>     v12 --> V13: Made each of bulk test cases(inovn.at  <http://ovn.at>) as separate
>>                   test to make it pass consistently.
>>     V11 --> V12: Minor fix inovn.at  <http://ovn.at>  to solve intermittent failures
>>
>>     V10 --> V11: Addressed review comments from V10 by Ihar
>>                 i) Expanded bulk updates test cases inovn.at  <http://ovn.at>
>>                    Overall below cases are covered
>>                    a) Attaches multiple mirrors (new)
>>                    b) Equal detaches and attaches (same as V10)
>>                    c) Detaches more than attaches (new)
>>                    d) Attaches more than detaches (new)
>>                    e) Detaches all (new)
>>                ii) Addressed the detach all case in mirror.c
>>               iii) Minor correction in NEWS
>>                iv) Added invalid mirror attach case inovn-nbctl.at  <http://ovn-nbctl.at>
>>
>>     Files modified (V10 --> V11):
>>     Code --> mirror.c
>>     Test -->ovn.at  <http://ovn.at>,ovn-nbctl.at  <http://ovn-nbctl.at>
>>     Misc --> NEWS
>>
>>     Ihar,
>>          Regarding mirror_delete function param delete_all it is wrt the
>>     port binding and if a port binding is removed we delete all its
>>     attachment. Already that use case is covered inovn.at  <http://ovn.at>.
>>     Having said that the detaches all had issue in mirror_delete which
>>     I have addressed. With all the above cases added now in bulk updates
>>     hope it should give good assurance.
>>
>>       NEWS                        |   1 +
>>       controller/automake.mk  <http://automake.mk>       |   4 +-
>>       controller/mirror.c         | 538 +++++++++++++++++++++++++
>>       controller/mirror.h         |  53 +++
>>       controller/ovn-controller.c | 266 ++++++++++--
>>       northd/en-northd.c          |   4 +
>>       northd/inc-proc-northd.c    |   4 +
>>       northd/northd.c             | 172 ++++++++
>>       northd/northd.h             |   2 +
>>       ovn-nb.ovsschema            |  31 +-
>>       ovn-nb.xml                  |  63 +++
>>       ovn-sb.ovsschema            |  26 +-
>>       ovn-sb.xml                  |  50 +++
>>       tests/ovn-nbctl.at  <http://ovn-nbctl.at>           | 120 ++++++
>>       tests/ovn-northd.at  <http://ovn-northd.at>          | 102 +++++
>>       tests/ovn.at  <http://ovn.at>                 | 778 ++++++++++++++++++++++++++++++++++++
>>       utilities/ovn-nbctl.c       | 357 +++++++++++++++++
>>       utilities/ovn-sbctl.c       |   4 +
>>       18 files changed, 2547 insertions(+), 28 deletions(-)
>>       create mode 100644 controller/mirror.c
>>       create mode 100644 controller/mirror.h
>>
>>     diff --git a/NEWS b/NEWS
>>     index 224a7b83e..84b22abdb 100644
>>     --- a/NEWS
>>     +++ b/NEWS
>>     @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
>>           any of LR's LRP IP, there is no need to create SNAT entry.  Now such
>>           traffic destined to LRP IP is not dropped.
>>         - Bump python version required for building OVN to 3.6.
>>     +  - Added Support for Remote Port Mirroring.
>>       
>>       OVN v22.06.0 - 03 Jun 2022
>>       --------------------------
>>     diff --git a/controller/automake.mk  <http://automake.mk>  b/controller/automake.mk  <http://automake.mk>
>>     index c2ab1bbe6..334672b4d 100644
>>     --- a/controller/automake.mk  <http://automake.mk>
>>     +++ b/controller/automake.mk  <http://automake.mk>
>>     @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>>       	controller/ovsport.h \
>>       	controller/ovsport.c \
>>       	controller/vif-plug.h \
>>     -	controller/vif-plug.c
>>     +	controller/vif-plug.c \
>>     +	controller/mirror.h \
>>     +	controller/mirror.c
>>       
>>       controller_ovn_controller_LDADD = lib/libovn.la  <http://libovn.la>  $(OVS_LIBDIR)/libopenvswitch.la  <http://libopenvswitch.la>
>>       man_MANS += controller/ovn-controller.8
>>     diff --git a/controller/mirror.c b/controller/mirror.c
>>     new file mode 100644
>>     index 000000000..11f2b63a6
>>     --- /dev/null
>>     +++ b/controller/mirror.c
>>     @@ -0,0 +1,538 @@
>>     +/* Copyright (c) 2022 Red Hat, Inc.
>>     + *
>>     + * Licensed under the Apache License, Version 2.0 (the "License");
>>     + * you may not use this file except in compliance with the License.
>>     + * You may obtain a copy of the License at:
>>     + *
>>     + *http://www.apache.org/licenses/LICENSE-2.0
>>     + *
>>     + * Unless required by applicable law or agreed to in writing, software
>>     + * distributed under the License is distributed on an "AS IS" BASIS,
>>     + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>>     + * See the License for the specific language governing permissions and
>>     + * limitations under the License.
>>     + */
>>     +
>>     +#include <config.h>
>>     +#include <unistd.h>
>>     +
>>     +/* library headers */
>>     +#include "lib/sset.h"
>>     +#include "lib/util.h"
>>     +
>>     +/* OVS includes. */
>>     +#include "lib/vswitch-idl.h"
>>     +#include "openvswitch/vlog.h"
>>     +
>>     +/* OVN includes. */
>>     +#include "binding.h"
>>     +#include "lib/ovn-sb-idl.h"
>>     +#include "mirror.h"
>>     +
>>     +VLOG_DEFINE_THIS_MODULE(port_mirror);
>>     +
>>     +/* Static function declarations */
>>     +
>>     +static const struct ovsrec_port *
>>     +get_port_for_iface(const struct ovsrec_interface *iface,
>>     +                  const struct ovsrec_bridge *br_int)
>>     +{
>>     +    for (size_t i = 0; i < br_int->n_ports; i++) {
>>     +        const struct ovsrec_port *p = br_int->ports[i];
>>     +        for (size_t j = 0; j < p->n_interfaces; j++) {
>>     +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
>>     +                return p;
>>     +            }
>>     +        }
>>     +    }
>>     +    return NULL;
>>     +}
>>     +
>>     +static bool
>>     +mirror_create(const struct sbrec_port_binding *pb,
>>     +              struct port_mirror_ctx *pm_ctx)
>>     +{
>>     +    const struct ovsrec_mirror *mirror = NULL;
>>     +
>>     +    if (pb->n_up && !pb->up[0]) {
>>     +        return true;
>>     +    }
>>     +
>>     +    if (pb->chassis != pm_ctx->chassis_rec) {
>>     +        return true;
>>     +    }
>>     +
>>     +    if (!pm_ctx->ovs_idl_txn) {
>>     +        return false;
>>     +    }
>>     +
>>     +
>>     +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>     Please remove the log message, it will pollute the log file
>     (please check for any other unnecessary log messages)
>>     +    /* Loop through the mirror rules */
>>     +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>>     +        /* check if the mirror already exists in OVS DB */
>>     +        bool create_mirror = true;
>>     +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>>     +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>     this is log(n^2), should we compute the names set once and then
>     check against it?
>>     +                /* Mirror with same name already exists
>>     +                 * No need to create mirror
>>     +                 */
>>     +                create_mirror = false;
>>     +                break;
>>     +            }
>>     +        }
>>     +
>>     +        if (create_mirror) {
>>     +
>>     +            struct smap options = SMAP_INITIALIZER(&options);
>>     +            char *port_name, *key;
>>     +
>>     +            key = xasprintf("%ld",(long int) pb->mirror_rules[i]->index);
>     use %d and then you don't need to cast to long int.
>
> %d if I use it, it gives a compiler warning as below.
> controller/mirror.c:90:31: warning: format ‘%d’ expects argument of 
> type ‘int’, but argument 2 has type ‘int64_t’ {aka ‘long int’} [-Wformat=]
>              key = xasprintf("%d", pb->mirror_rules[i]->index);
> If I just use %ld and don't do the type casting the normal builds pass 
> well on my pc. But when I commit the patch 1 or 2 builds fail in the 
> github build
> I think mostly the "linux gcc m32..." ones.
> Already this I had kept it without typecast before and then had to add 
> type cast with no other option left since the github tests were not 
> passing without it.


Makes sense, thanks for the explanation.


>>     +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
>>     +            smap_add(&options, "key", key);
>>     +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>>     +                /* Set the ERSPAN index */
>>     +                smap_add(&options, "erspan_idx", key);
>>     +                smap_add(&options, "erspan_ver","1");
>>     +
>>     +            }
>>     +            struct ovsrec_interface *iface =
>>     +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>>     +            port_name = xasprintf("ovn-%s",
>>     +                                   pb->mirror_rules[i]->name);
>>     +
>>     +            ovsrec_interface_set_name(iface, port_name);
>>     +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
>>     +            ovsrec_interface_set_options(iface, &options);
>
>     the code calculating the interface options map duplicates what's
>     done in check_and_update_interface_table. Consider putting the
>     code to construct the options map into a helper function.
>
>>     +
>>     +            struct ovsrec_port *port =
>>     +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>>     +            ovsrec_port_set_name(port, port_name);
>>     +            ovsrec_port_set_interfaces(port, &iface, 1);
>>     +
>>     +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>>     +
>>     +            smap_destroy(&options);
>>     +            free(port_name);
>>     +            free(key);
>>     +
>>     +            VLOG_INFO("Creating Mirror in OVS DB");
>     remove the log message
>>     +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>>     +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>>     +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
>>     +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>>     +                                                             mirror);
>>     +        }
>>     +
>>     +        struct local_binding *lbinding = local_binding_find(
>>     +                               pm_ctx->local_bindings, pb->logical_port);
>>     +        const struct ovsrec_port *p =
>>     +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
>>     +        if (p) {
>>     +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>>     +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>     +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>>     +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>     +            } else {
>>     +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>>     +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>>     +            }
>>     +        }
>>     +    }
>>     +    return true;
>>     +}
>>     +
>>     +static void
>>     +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
>>     +                              struct ovsrec_mirror *ovs_mirror)
>>     +{
>>     +    char *filter;
>>     +    if ((ovs_mirror->n_select_dst_port)
>>     +            && (ovs_mirror->n_select_src_port)) {
>>     +        filter = "both";
>>     +    } else if (ovs_mirror->n_select_dst_port) {
>>     +        filter = "to-lport";
>>     +    } else {
>>     +        filter = "from-lport";
>>     +    }
>     nit: I would avoid strcmp below and instead introduce a enum for
>     the filter type and then switch() over it below. Integer
>     comparisons are cheaper than string.
>>     +
>>     +    if (strcmp(sb_mirror->filter, filter)) {
>>     +        if (!strcmp(sb_mirror->filter,"from-lport")
>>     +                              && !strcmp(filter,"both")) {
>     add spaces after , - here and below (please fix the whole patch),
>     I think these should have been captured by checkpatch script, no?
>
> Sure. Got it. Will correct everywhere else. Nope.. checkpatch 
> didn't complain!
>
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>>     +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_dst_port[i]);
>>     +            }
>>     +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>     +                              && !strcmp(filter,"both")) {
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>>     +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_src_port[i]);
>>     +            }
>>     +        } else if (!strcmp(sb_mirror->filter,"both")
>>     +                              && !strcmp(filter,"from-lport")) {
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>>     +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_src_port[i]);
>>     +            }
>>     +        } else if (!strcmp(sb_mirror->filter,"both")
>>     +                              && !strcmp(filter,"to-lport")) {
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>>     +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_dst_port[i]);
>>     +            }
>>     +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>>     +                              && !strcmp(filter,"from-lport")) {
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>>     +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_src_port[i]);
>>     +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_src_port[i]);
>>     +            }
>>     +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>>     +                              && !strcmp(filter,"to-lport")) {
>>     +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>>     +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_dst_port[i]);
>>     +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>>     +                                             ovs_mirror->select_dst_port[i]);
>>     +            }
>>     +        }
>>     +    }
>>     +}
>>     +
>>     +static void
>>     +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
>>     +                                   struct ovsrec_mirror *ovs_mirror)
>     please align function parameters here and in other places with spaces
>>     +{
>>     +    struct smap options = SMAP_INITIALIZER(&options);
>>     +    char *key, *type;
>>     +    struct ovsrec_interface *iface =
>>     +                          ovs_mirror->output_port->interfaces[0];
>>     +    struct smap *opts = &iface->options;
>>     +
>>     +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>>     +    if (erspan_ver) {
>>     +        type = "erspan";
>>     +    } else {
>>     +        type = "gre";
>>     +    }
>>     +    if (strcmp(type, sb_mirror->type)) {
>>     +        ovsrec_interface_set_type(iface, sb_mirror->type);
>>     +    }
>>     +
>>     +    key = xasprintf("%ld",(long int) sb_mirror->index);
>     Can't you just switch to "%d" and avoid (long int) casting?
>
> Nope. For the same reason explained in another one above.
>
>>     +    smap_add(&options, "remote_ip", sb_mirror->sink);
>>     +    smap_add(&options, "key", key);
>>     +
>>     +    if (!strcmp(sb_mirror->type, "erspan")) {
>>     +        /* Set the ERSPAN index */
>>     +        smap_add(&options, "erspan_idx", key);
>>     +        smap_add(&options, "erspan_ver","1");
>     add space before "1"
>>     +    }
>>     +
>>     +    ovsrec_interface_set_options(iface, &options);
>>     +    smap_destroy(&options);
>>     +    free(key);
>>     +
>     remove the empty line
>>     +}
>>     +
>>     +static void
>>     +mirror_update(const struct sbrec_mirror *sb_mirror,
>>     +              struct ovsrec_mirror *ovs_mirror)
>>     +{
>>     +    check_and_update_interface_table(sb_mirror, ovs_mirror);
>>     +
>     nit: remove the empty line, it doesn't seem to serve any need
>>     +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>>     +}
>>     +
>>     +static bool
>>     +mirror_delete(const struct sbrec_port_binding *pb,
>>     +              struct port_mirror_ctx *pm_ctx,
>>     +              struct shash *pb_mirror_map,
>>     +              bool delete_all)
>>     +{
>>     +
>>     +    if (!pm_ctx->ovs_idl_txn) {
>>     +        return false;
>>     +    }
>>     +
>>     +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>>     +
>>     +    if (!delete_all) {
>>     +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>     +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>>     +        }
>>     +    }
>>     +
>>     +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
>
btw remove && pb->n_mirror_rules here, it serves no need since for below 
won't execute
>
>>     +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>>     +
>>     +            struct ovsrec_mirror *ovs_mirror = NULL;
>>     +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>>     +                                            pb->mirror_rules[i]->name);
>>     +            if (ovs_mirror) {
>>     +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>     +                                               ovs_mirror->output_port);
>>     +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>     +                                                            ovs_mirror);
>>     +                ovsrec_port_delete(ovs_mirror->output_port);
>>     +                ovsrec_mirror_delete(ovs_mirror);
>>     +            }
>>     +        }
>>     +    }
>>     +
>>     +    struct shash_node *mirror_node;
>>     +    const struct sbrec_port_binding *sb_pb;
>>     +    int attach_cnt = 0;
>     it should be bool since you use it for two states only; also move
>     its definition closer to where it's used (under if !sset_find branch)
>>     +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>>     +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>>     +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>>     +            /* Find if the mirror has other sources */
>>     +            attach_cnt = 0;
>>     +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
>>     +                                       pm_ctx->port_binding_table) {
>     if I understand it correctly, here for every pb, you walk through
>     the whole list of pbs to check if any of them also use the mirror;
>     making this operation very expensive. it seems to me that a proper
>     structure like a map of mirror-to-(set of port names) should be
>     used to avoid this.
>>     +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
>>     +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
>>     +                                                ovs_mirror->name)) {
>>     +                        attach_cnt++;
>     ...and you could probably bail out from here since the only thing
>     you really care is that at least one other pb is attached to the
>     mirror
>>     +                    }
>>     +                }
>>     +            }
>>     +            if (attach_cnt) {
>>     +                /* More than 1 source then just
>>     +                 * update the mirror table
>>     +                 */
>>     +                bool done = false;
>>     +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
>>     +                                                   && (done == false)); i++) {
>     transform done == false condition into if (done) break at the end
>     of the for-loop
>>     +                    const struct ovsrec_port *port_rec =
>>     +                                               ovs_mirror->select_dst_port[i];
>>     +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>     +                        const struct ovsrec_interface *iface_rec;
>>     +
>>     +                        iface_rec = port_rec->interfaces[j];
>>     +                        const char *iface_id =
>>     +                                            smap_get(&iface_rec->external_ids,
>>     +                                                                  "iface-id");
>>     +                        if (!strcmp(iface_id,pb->logical_port)) {
>>     +                            ovsrec_mirror_update_select_dst_port_delvalue(
>>     +                                                        ovs_mirror, port_rec);
>>     +                            done = true;
>>     +                            break;
>>     +                        }
>>     +                    }
>>     +                }
>>     +                done = false;
>>     +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
>>     +                                                   && (done == false)); i++) {
>>     +                    const struct ovsrec_port *port_rec =
>>     +                                                ovs_mirror->select_src_port[i];
>>     +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>     +                        const struct ovsrec_interface *iface_rec;
>>     +
>>     +                        iface_rec = port_rec->interfaces[j];
>>     +                        const char *iface_id =
>>     +                                            smap_get(&iface_rec->external_ids,
>>     +                                                                  "iface-id");
>>     +                        if (!strcmp(iface_id,pb->logical_port)) {
>>     +                            ovsrec_mirror_update_select_src_port_delvalue(
>>     +                                                        ovs_mirror, port_rec);
>>     +                            done = true;
>>     +                            break;
>>     +                        }
>>     +                    }
>>     +                }
>>     +            } else {
>>     +                /*
>>     +                 * If only 1 source delete the output port
>>     +                 * and then delete the mirror completely
>>     +                 */
>>     +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
>>     +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>     +                                                    ovs_mirror->output_port);
>>     +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>     +                                                            ovs_mirror);
>>     +                ovsrec_port_delete(ovs_mirror->output_port);
>>     +                ovsrec_mirror_delete(ovs_mirror);
>>     +            }
>>     +        }
>>     +    }
>>     +
>>     +    const char *used_node, *used_next;
>>     +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>>     +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>>     +    }
>>     +    sset_destroy(&pb_mirrors);
>>     +
>>     +    return true;
>>     +}
>>     +
>>     +static void
>>     +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>>     +                            struct port_mirror_ctx *pm_ctx,
>>     +                            struct shash *pb_mirror_map)
>>     +{
>>     +    const struct ovsrec_mirror *mirror = NULL;
>>     +
>>     +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>>     +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>>     +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
>>     +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>     +                const struct ovsrec_interface *iface_rec;
>>     +                iface_rec = port_rec->interfaces[j];
>>     +                const char *logical_port =
>>     +                    smap_get(&iface_rec->external_ids, "iface-id");
>>     +                if (!strcmp(logical_port, pb->logical_port)) {
>>     +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>>     +                }
>>     +            }
>>     +        }
>>     +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>>     +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
>>     +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>>     +                const struct ovsrec_interface *iface_rec;
>>     +                iface_rec = port_rec->interfaces[j];
>>     +                const char *logical_port =
>>     +                    smap_get(&iface_rec->external_ids, "iface-id");
>>     +                if (!strcmp(logical_port, pb->logical_port)) {
>>     +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>>     +                }
>>     +            }
>>     +        }
>>     +    }
>>     +}
>>     +
>>     +void
>>     +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>     +{
>>     +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>>     +
>>     +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
>>     +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
>>     +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
>>     +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
>>     +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
>>     +}
>>     +
>>     +
>>     +void
>>     +ovn_port_mirror_init(struct shash *ovs_mirrors)
>>     +{
>>     +    shash_init(ovs_mirrors);
>>     +}
>>     +
>>     +void
>>     +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>>     +{
>>     +    const struct sbrec_port_binding *pb;
>>     +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>>     +                                       pm_ctx->port_binding_table) {
>>     +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>>     +    }
>>     +}
>>     +
>>     +bool
>>     +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
>>     +                     struct port_mirror_ctx *pm_ctx)
>>     +{
>>     +    bool ret = true;
>>     +    struct local_binding *lbinding = local_binding_find(
>>     +                               pm_ctx->local_bindings, pb->logical_port);
>>     +
>>     +    if (strcmp(pb->type, "") && (!lbinding)) {
>>     +        return ret;
>>     +    }
>>     +
>>     +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
>>     +
>>     +    /* Need to find if mirror needs update */
>>     +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>>     +    if (!removed) {
>>     +        if ((pb->n_mirror_rules == 0)
>>     +              && (shash_is_empty(&port_ovs_mirrors))) {
>>     +            /* No mirror update */
>     I think this branch is redundant; the next one will do exactly the
>     same for the case of n_mirror_rules = 0
>>     +        } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) {
>>     +            /* Though number of mirror rules are same,
>     "Though the number of mirror rules is the same, need to verify the
>     contents"
>>     +             * need to verify the contents
>>     +             */
>>     +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
>>     +                if (!shash_find(&port_ovs_mirrors,
>>     +                               pb->mirror_rules[i]->name)) {
>>     +                    /* Mis match in OVN SB DB and OVS DB
>>     +                     * Delete and Create mirror(s) with proper sources
>>     +                     */
>>     +                    ret = mirror_delete(pb, pm_ctx,
>>     +                                        &port_ovs_mirrors, false);
>>     +                    if (ret) {
>>     +                        ret = mirror_create(pb, pm_ctx);
>>     +                    }
>>     +                    break;
>>     +                }
>>     +            }
>>     +        } else {
>     please flatten this out so that there's a single if-else layer for
>     all cases
>>     +            /* Update Mirror */
>>     +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
>>     +                /* create mirror,
>>     +                 * if mirror already exists only update selection
>>     +                 */
>>     +                ret = mirror_create(pb, pm_ctx);
>     I still feel this is incorrect. The fact that pb is attached to a
>     larger number of mirrors doesn't necessarily mean that it was
>     *only* attached to new mirrors in this transaction, and that it
>     was *not* detached from other mirrors in the same transaction.
>     Consider that in the same transaction, a port binding was attached
>     to 2 more mirrors AND detached from 1 existing mirror; in this
>     case, only mirror_create() will be called, so the old attachment
>     won't be cleared.
>
>
> Specifically I tried the below bulk test for the transactions you 
> mentioned and output was as expected and it passed.
> I didn't find any issue here.
> Please let me know if this is the case you said.
>
> #Initial state is ls1-lp1 will be attached only to mirror2 and mirror1 
> and mirror0 dont have any attachments.
>
> # Attaches THE SAME port to multiple mirrors
> # and detach it from existing mirror
>
> check as hv1 ovn-appctl -t ovn-controller debug/pause
> check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
> check as hv1 ovn-appctl -t ovn-controller debug/resume
> check ovn-nbctl --wait=hv sync
>
> as hv1
> new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>
> AT_CHECK([test "$origA" = "$new1"], [0], [])
> AT_CHECK([test "$origA" = "$new2"], [0], [])
> OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])


And what if the mirror was attached to another port before the test? 
Regardless, please add the test case to the suite. I may have to play 
with it locally a bit more to try to invoke the error state. Perhaps you 
are right though.


>
>>     +            } else {
>>     +                /* delete mirror,
>>     +                 * if mirror has other sources only update selection
>>     +                 */
>>     +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
>
>     the same problem here: even if the number of mirror attachments
>     reduced, it doesn't necessarily mean that the port was not
>     attached to new mirrors as part of the transaction.
>
>     I've looked through your test cases and I don't see a single case
>     where you would detach / attach a different number of mirrors to
>     THE SAME port in scope of a single transaction. Please add the
>     test cases to cover this code.
>
> Added test as above.  Please see if that is fine.
>
>>     +            }
>>     +        }
>>     +    } else {
>>     +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>     I'm puzzled by this branch. So if a pb is removed, then you are
>     going to delete all mirrors that were attached to it
>     (delete_all=true) regardless of any other ports relying on the
>     mirror. Am I interpreting it correctly? If so, why is it the right
>     thing to do? Perhaps adding some more comments in this function
>     explaining which branches handle which scenarios would help me
>     understand what's happening. Thanks.
>
> No. It cannot delete all blindly for the reason you have told.  I feel 
> the variable name needs correction to detach_all.
> If a port binding is deleted then all its attachments are deleted and 
> if that was the only attachment then it will delete mirror as well and 
> the output port associated with it.
> It's working as expected. Maybe the renaming of the bool variable will 
> help. And yeah, I will add some more comments.


Looking forward to the updated version with comments and better variable 
names.


That said, doesn't mirror_delete also call ovsrec_mirror_delete which 
will kill the mirror in OVS database, regardless of whether it's 
attached to any other port?


>>     +    }
>>     +
>>     +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>     +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>     +                                              &port_ovs_mirrors) {
>>     +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>>     +    }
>>     +    shash_destroy(&port_ovs_mirrors);
>>     +
>>     +    return ret;
>>     +}
>>     +
>>     +bool
>>     +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>>     +{
>>     +    const struct sbrec_mirror *mirror = NULL;
>>     +    struct ovsrec_mirror *ovs_mirror = NULL;
>>     +
>>     +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
>>     +    /* For each tracked mirror entry check if OVS entry is there*/
>     nit: add space before */
>>     +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
>>     +        if (ovs_mirror) {
>>     +            if (sbrec_mirror_is_deleted(mirror)) {
>>     +                /* Need to delete the mirror in OVS */
>>     +                VLOG_INFO("Delete mirror and remove port");
>     remove this log message, it will pollute log file unnecessarily
>>     +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>>     +                                                    ovs_mirror->output_port);
>>     +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>>     +                                                      ovs_mirror);
>>     +                ovsrec_port_delete(ovs_mirror->output_port);
>>     +                ovsrec_mirror_delete(ovs_mirror);
>>     +            } else {
>>     +                mirror_update(mirror, ovs_mirror);
>>     +            }
>>     +        }
>>     +    }
>>     +
>>     +    return true;
>>     +}
>>     +
>>     +void
>>     +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>>     +{
>>     +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>     +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>     +                                              ovs_mirrors) {
>>     +        shash_delete(ovs_mirrors, ovs_mirror_node);
>>     +    }
>>     +    shash_destroy(ovs_mirrors);
>>     +}
>>     diff --git a/controller/mirror.h b/controller/mirror.h
>>     new file mode 100644
>>     index 000000000..85b964f55
>>     --- /dev/null
>>     +++ b/controller/mirror.h
>>     @@ -0,0 +1,53 @@
>>     +/* Copyright (c) 2022 Red Hat, Inc.
>>     + *
>>     + * Licensed under the Apache License, Version 2.0 (the "License");
>>     + * you may not use this file except in compliance with the License.
>>     + * You may obtain a copy of the License at:
>>     + *
>>     + *http://www.apache.org/licenses/LICENSE-2.0
>>     + *
>>     + * Unless required by applicable law or agreed to in writing, software
>>     + * distributed under the License is distributed on an "AS IS" BASIS,
>>     + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>>     + * See the License for the specific language governing permissions and
>>     + * limitations under the License.
>>     + */
>>     +
>>     +#ifndef OVN_MIRROR_H
>>     +#define OVN_MIRROR_H 1
>>     +
>>     +struct ovsdb_idl_txn;
>>     +struct ovsrec_port_table;
>>     +struct ovsrec_bridge;
>>     +struct ovsrec_bridge_table;
>>     +struct ovsrec_open_vswitch_table;
>>     +struct sbrec_chassis;
>>     +struct ovsrec_interface_table;
>>     +struct ovsrec_mirror_table;
>>     +struct sbrec_mirror_table;
>>     +struct sbrec_port_binding_table;
>>     +
>>     +struct port_mirror_ctx {
>>     +    struct shash *ovs_mirrors;
>>     +    struct ovsdb_idl_txn *ovs_idl_txn;
>>     +    const struct ovsrec_port_table *port_table;
>>     +    const struct ovsrec_bridge *br_int;
>>     +    const struct sbrec_chassis *chassis_rec;
>>     +    const struct ovsrec_bridge_table *bridge_table;
>>     +    const struct ovsrec_open_vswitch_table *ovs_table;
>>     +    const struct ovsrec_interface_table *iface_table;
>>     +    const struct ovsrec_mirror_table *mirror_table;
>>     +    const struct sbrec_mirror_table *sb_mirror_table;
>>     +    const struct sbrec_port_binding_table *port_binding_table;
>>     +    struct shash *local_bindings;
>>     +};
>>     +
>>     +void mirror_register_ovs_idl(struct ovsdb_idl *);
>>     +void ovn_port_mirror_init(struct shash *);
>>     +void ovn_port_mirror_destroy(struct shash *);
>>     +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>>     +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
>>     +                                  bool removed,
>>     +                                  struct port_mirror_ctx *pm_ctx);
>>     +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
>>     +#endif
>>     diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>>     index 8895c7a2b..15ab17c4a 100644
>>     --- a/controller/ovn-controller.c
>>     +++ b/controller/ovn-controller.c
>>     @@ -78,6 +78,7 @@
>>       #include "lib/inc-proc-eng.h"
>>       #include "lib/ovn-l7.h"
>>       #include "hmapx.h"
>>     +#include "mirror.h"
>>       
>>       VLOG_DEFINE_THIS_MODULE(main);
>>       
>>     @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>           ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>>           ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>>           ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
>>     +    mirror_register_ovs_idl(ovs_idl);
>>       }
>>       
>>       #define SB_NODES \
>>     @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>>           SB_NODE(load_balancer, "load_balancer") \
>>           SB_NODE(fdb, "fdb") \
>>           SB_NODE(meter, "meter") \
>>     +    SB_NODE(mirror, "mirror") \
>>           SB_NODE(static_mac_binding, "static_mac_binding")
>>       
>>       enum sb_engine_node {
>>     @@ -1003,7 +1006,8 @@ enum sb_engine_node {
>>           OVS_NODE(bridge, "bridge") \
>>           OVS_NODE(port, "port") \
>>           OVS_NODE(interface, "interface") \
>>     -    OVS_NODE(qos, "qos")
>>     +    OVS_NODE(qos, "qos") \
>>     +    OVS_NODE(mirror, "mirror")
>>       
>>       enum ovs_engine_node {
>>       #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>>     @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>>           free(lbs);
>>       }
>>       
>>     +/* Mirror Engine */
>>     +struct ed_type_port_mirror {
>>     +    struct shash ovs_mirrors;
>>     +};
>>     +
>>     +static void *
>>     +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>>     +                    struct engine_arg *arg OVS_UNUSED)
>>     +{
>>     +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
>>     +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>>     +    return port_mirror;
>>     +}
>>     +
>>     +static void
>>     +en_port_mirror_cleanup(void *data)
>>     +{
>>     +    struct ed_type_port_mirror *port_mirror = data;
>>     +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>>     +}
>>     +
>>     +static void
>>     +init_port_mirror_ctx(struct engine_node *node,
>>     +                 struct ed_type_runtime_data *rt_data,
>>     +                 struct ed_type_port_mirror *port_mirror_data,
>>     +                 struct port_mirror_ctx *pm_ctx)
>>     +{
>>     +    struct ovsrec_open_vswitch_table *ovs_table =
>>     +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
>>     +            engine_get_input("OVS_open_vswitch", node));
>>     +    struct ovsrec_bridge_table *bridge_table =
>>     +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
>>     +            engine_get_input("OVS_bridge", node));
>>     +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>>     +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
>>     +
>>     +    ovs_assert(br_int && chassis_id);
>>     +    const struct sbrec_chassis *chassis = NULL;
>>     +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>>     +        engine_ovsdb_node_get_index(
>>     +                engine_get_input("SB_chassis", node),
>>     +                "name");
>>     +
>>     +    if (chassis_id) {
>>     +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
>>     +    }
>>     +    ovs_assert(chassis);
>>     +
>>     +    struct ovsrec_port_table *port_table =
>>     +        (struct ovsrec_port_table *) EN_OVSDB_GET(
>>     +            engine_get_input("OVS_port", node));
>>     +
>>     +    struct ed_type_ovs_interface_shadow *iface_shadow =
>>     +        engine_get_input_data("ovs_interface_shadow", node);
>>     +
>>     +    struct ovsrec_mirror_table *mirror_table =
>>     +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
>>     +            engine_get_input("OVS_mirror", node));
>>     +
>>     +    struct sbrec_port_binding_table *pb_table =
>>     +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
>>     +            engine_get_input("SB_port_binding", node));
>>     +
>>     +    struct sbrec_mirror_table *sb_mirror_table =
>>     +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
>>     +            engine_get_input("SB_mirror", node));
>>     +
>>     +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>>     +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>>     +                           &port_mirror_data->ovs_mirrors) {
>>     +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
>>     +    }
>>     +
>>     +    const struct ovsrec_mirror *ovsmirror = NULL;
>>     +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>>     +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
>>     +    }
>>     +
>>     +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>>     +    pm_ctx->port_table = port_table;
>>     +    pm_ctx->iface_table = iface_shadow->iface_table;
>>     +    pm_ctx->mirror_table = mirror_table;
>>     +    pm_ctx->port_binding_table = pb_table;
>>     +    pm_ctx->sb_mirror_table = sb_mirror_table;
>>     +    pm_ctx->br_int = br_int;
>>     +    pm_ctx->chassis_rec = chassis;
>>     +    pm_ctx->bridge_table = bridge_table;
>>     +    pm_ctx->ovs_table = ovs_table;
>>     +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>>     +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>>     +}
>>     +
>>     +static void
>>     +en_port_mirror_run(struct engine_node *node, void *data)
>>     +{
>>     +    struct port_mirror_ctx pm_ctx;
>>     +    struct ed_type_port_mirror *port_mirror_data = data;
>>     +    struct ed_type_runtime_data *rt_data =
>>     +        engine_get_input_data("runtime_data", node);
>>     +
>>     +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>     +
>>     +    ovn_port_mirror_run(&pm_ctx);
>>     +    engine_set_node_state(node, EN_UPDATED);
>>     +}
>>     +
>>     +static bool
>>     +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
>>     +{
>>     +    struct ed_type_runtime_data *rt_data =
>>     +        engine_get_input_data("runtime_data", node);
>>     +
>>     +    /* There is no tracked data. Fall back to full recompute of port_mirror */
>>     +    if (!rt_data->tracked) {
>>     +        return false;
>>     +    }
>>     +
>>     +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
>>     +    if (hmap_is_empty(tracked_dp_bindings)) {
>>     +        return true;
>>     +    }
>>     +
>>     +    struct port_mirror_ctx pm_ctx;
>>     +    struct ed_type_port_mirror *port_mirror_data = data;
>>     +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>     +
>>     +    struct tracked_datapath *tdp;
>>     +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>>     +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>>     +            /* Fall back to full recompute when a local datapath
>>     +             * is added or deleted. */
>>     +            return false;
>>     +        }
>>     +
>>     +        struct shash_node *shash_node;
>>     +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>>     +            struct tracked_lport *lport = shash_node->data;
>>     +            bool removed =
>>     +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
>>     +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
>>     +                return false;
>>     +            }
>>     +        }
>>     +    }
>>     +
>>     +    engine_set_node_state(node, EN_UPDATED);
>>     +    return true;
>>     +}
>>     +
>>     +static bool
>>     +port_mirror_port_binding_handler(struct engine_node *node, void *data)
>>     +{
>>     +    struct port_mirror_ctx pm_ctx;
>>     +    struct ed_type_port_mirror *port_mirror_data = data;
>>     +    struct ed_type_runtime_data *rt_data =
>>     +        engine_get_input_data("runtime_data", node);
>>     +
>>     +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>     +
>>     +    /* handle port binding updates (i.,e when the mirror column
>
>     "i.e."
>
>>     +     * of port_binding is updated)
>>     +     */
>>     +    const struct sbrec_port_binding *pb;
>>     +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>>     +                                               pm_ctx.port_binding_table) {
>>     +        bool removed = sbrec_port_binding_is_deleted(pb);
>>     +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
>>     +            return false;
>>     +        }
>>     +    }
>>     +
>>     +    engine_set_node_state(node, EN_UPDATED);
>>     +    return true;
>>     +
>>     +}
>>     +
>>     +static bool
>>     +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
>>     +{
>>     +    struct port_mirror_ctx pm_ctx;
>>     +    struct ed_type_port_mirror *port_mirror_data = data;
>>     +    struct ed_type_runtime_data *rt_data =
>>     +        engine_get_input_data("runtime_data", node);
>>     +
>>     +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>>     +
>>     +    /* handle sb mirror updates
>>     +     */
>     nit: combine two lines above
>>     +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>>     +        return false;
>>     +    }
>>     +
>>     +    engine_set_node_state(node, EN_UPDATED);
>>     +    return true;
>>     +
>>     +}
>>     +
>>       /* Engine node which is used to handle the Non VIF data like
>>        *   - OVS patch ports
>>        *   - Tunnel ports and the related chassis information.
>>     @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
>>           ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>>           ENGINE_NODE(northd_options, "northd_options");
>>           ENGINE_NODE(dhcp_options, "dhcp_options");
>>     +    ENGINE_NODE(port_mirror, "port_mirror");
>>       
>>       #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>>           SB_NODES
>>     @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
>>           engine_add_input(&en_flow_output, &en_pflow_output,
>>                            flow_output_pflow_output_handler);
>>       
>>     +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
>>     +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>>     +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>>     +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>>     +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
>>     +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>>     +                     engine_noop_handler);
>>     +    engine_add_input(&en_flow_output, &en_port_mirror,
>>     +                     engine_noop_handler);
>     perhaps I am missing something, but why would flow_output depend
>     on mirror updates, when it seems like the only action needed is
>     mapping ovn mirror definitions to ovs mirror table?
>
> One thing for sure is, without this it wont work properly.
> Along with port binding changes we handle runtime data changes as 
> well. And to get that call
> this noop_handler for en_flow_output was necessary else I 
> wasn't getting those calls. (Maybe someone who knows
> Engine working well can comment on why. I am not sure.)
> This was probably the first change when I added a new engine node as 
> suggested by Numan.
> To reconfirm I just commented above and tried running the Mirror test 
> and it fails in ovn.at <http://ovn.at>


Thanks for confirming. As I said before, I'm no knowledgeable in I-P 
engine. I will have to play with the patch locally myself to understand 
why flow output depends on mirrors.


>>     +    engine_add_input(&en_port_mirror, &en_runtime_data,
>>     +                     port_mirror_runtime_data_handler);
>>     +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>>     +                     port_mirror_sb_mirror_handler);
>>     +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>>     +                     port_mirror_port_binding_handler);
>>     +
>>           struct engine_arg engine_arg = {
>>               .sb_idl = ovnsb_idl_loop.idl,
>>               .ovs_idl = ovs_idl_loop.idl,
>>     @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
>>       
>>                           stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
>>                                           time_msec());
>>     -                    if (ovnsb_idl_txn) {
>>     -                        if (ofctrl_has_backlog()) {
>>     -                            /* When there are in-flight messages pending to
>>     -                             * ovs-vswitchd, we should hold on recomputing so
>>     -                             * that the previous flow installations won't be
>>     -                             * delayed.  However, we still want to try if
>>     -                             * recompute is not needed and we can quickly
>>     -                             * incrementally process the new changes, to avoid
>>     -                             * unnecessarily forced recomputes later on.  This
>>     -                             * is because the OVSDB change tracker cannot
>>     -                             * preserve tracked changes across iterations.  If
>>     -                             * change tracking is improved, we can simply skip
>>     -                             * this round of engine_run and continue processing
>>     -                             * acculated changes incrementally later when
>>     -                             * ofctrl_has_backlog() returns false. */
>>     -                            engine_run(false);
>>     -                        } else {
>>     -                            engine_run(true);
>>     -                        }
>>     -                    } else {
>>     -                        /* Even if there's no SB DB transaction available,
>>     +
>>     +                    bool allow_engine_recompute = true;
>>     +
>>     +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
>>     +                                                     ofctrl_has_backlog()) {
>>     +                        /* When there are in-flight messages pending to
>>     +                         * ovs-vswitchd, we should hold on recomputing so
>>     +                         * that the previous flow installations won't be
>>     +                         * delayed.  However, we still want to try if
>>     +                         * recompute is not needed and we can quickly
>>     +                         * incrementally process the new changes, to avoid
>>     +                         * unnecessarily forced recomputes later on.  This
>>     +                         * is because the OVSDB change tracker cannot
>>     +                         * preserve tracked changes across iterations.  If
>>     +                         * change tracking is improved, we can simply skip
>>     +                         * this round of engine_run and continue processing
>>     +                         * acculated changes incrementally later when
>>     +                         * ofctrl_has_backlog() returns false. */
>>     +
>>     +                        /* Even if there's no SB/OVS DB transaction available,
>>                                * try to run the engine so that we can handle any
>>                                * incremental changes that don't require a recompute.
>>                                * If a recompute is required, the engine will abort,
>>                                * triggerring a full run in the next iteration.
>>                                */
>>     -                        engine_run(false);
>>     +                        allow_engine_recompute = false;
>>                           }
>>     +
>>     +                    engine_run(allow_engine_recompute);
>>     +
>>                           stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
>>                                          time_msec());
>>                           if (engine_has_updated()) {
>>     diff --git a/northd/en-northd.c b/northd/en-northd.c
>>     index 7fe83db64..608220b1f 100644
>>     --- a/northd/en-northd.c
>>     +++ b/northd/en-northd.c
>>     @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void *data)
>>               EN_OVSDB_GET(engine_get_input("NB_acl", node));
>>           input_data.nbrec_static_mac_binding_table =
>>               EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>>     +    input_data.nbrec_mirror_table =
>>     +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>>       
>>           input_data.sbrec_sb_global_table =
>>               EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>>     @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, void *data)
>>               EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>>           input_data.sbrec_static_mac_binding_table =
>>               EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>>     +    input_data.sbrec_mirror_table =
>>     +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>>       
>>           northd_run(&input_data, data,
>>                      eng_ctx->ovnnb_idl_txn,
>>     diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>>     index 54e0ad3b0..ac27a730e 100644
>>     --- a/northd/inc-proc-northd.c
>>     +++ b/northd/inc-proc-northd.c
>>     @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>           NB_NODE(acl, "acl") \
>>           NB_NODE(logical_router, "logical_router") \
>>           NB_NODE(qos, "qos") \
>>     +    NB_NODE(mirror, "mirror") \
>>           NB_NODE(meter, "meter") \
>>           NB_NODE(meter_band, "meter_band") \
>>           NB_NODE(logical_router_port, "logical_router_port") \
>>     @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>>           SB_NODE(logical_flow, "logical_flow") \
>>           SB_NODE(logical_dp_group, "logical_DP_group") \
>>           SB_NODE(multicast_group, "multicast_group") \
>>     +    SB_NODE(mirror, "mirror") \
>>           SB_NODE(meter, "meter") \
>>           SB_NODE(meter_band, "meter_band") \
>>           SB_NODE(datapath_binding, "datapath_binding") \
>>     @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>           engine_add_input(&en_northd, &en_nb_acl, NULL);
>>           engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>>           engine_add_input(&en_northd, &en_nb_qos, NULL);
>>     +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>>           engine_add_input(&en_northd, &en_nb_meter, NULL);
>>           engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>>           engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
>>     @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>           engine_add_input(&en_northd, &en_sb_address_set, NULL);
>>           engine_add_input(&en_northd, &en_sb_port_group, NULL);
>>           engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>>     +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>>           engine_add_input(&en_northd, &en_sb_meter, NULL);
>>           engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>>           engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>>     diff --git a/northd/northd.c b/northd/northd.c
>>     index b7388afc5..52abdda28 100644
>>     --- a/northd/northd.c
>>     +++ b/northd/northd.c
>>     @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
>>           free(requested_chassis_sb);
>>       }
>>       
>>     +static void
>>     +do_sb_mirror_addition(struct northd_input *input_data,
>>     +                      const struct ovn_port *op)
>>     +{
>>     +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>     +        const struct sbrec_mirror *sb_mirror;
>>     +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>>     +                                     input_data->sbrec_mirror_table) {
>>     +            if (!strcmp(sb_mirror->name,
>>     +                        op->nbsp->mirror_rules[i]->name)) {
>>     +                /* Add the value to SB */
>>     +                sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>>     +                                                                sb_mirror);
>>     +            }
>>     +        }
>>     +    }
>>     +}
>>     +
>>     +static void
>>     +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
>>     +                                       const struct ovn_port *op)
>>     +{
>>     +    size_t i = 0;
>>     +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>>     +        /* Needs deletion in SB */
>>     +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
>>     +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>     +            shash_add(&nb_mirror_rules,
>>     +                                 op->nbsp->mirror_rules[i]->name,
>>     +                                 op->nbsp->mirror_rules[i]);
>>     +        }
>>     +
>>     +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>     +            if (!shash_find(&nb_mirror_rules,
>>     +                           op->sb->mirror_rules[i]->name)) {
>>     +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>     +                                                 op->sb->mirror_rules[i]);
>     this will only delete old mirror rules; but it won't add any new rules
>
> But new rules get added in the else case right?
Not necessarily. You may have 2 mirror attachments removed and 1 added. 
In this case, sb->n_mirror_rules > nbsp->n_mirror_rules, and the else 
branch won't be called. (And vice versa.)
> From my understanding we get a port binding update for every change 
> and that's what I had noticed during my testing
> I used to get separate SB port binding update calls for each port 
> binding change.
>
> So, at any point of time for a particular port binding it can be in 3 
> states when some change is triggered
> a) SB has more mirror rules and NB has less
> b) NB has more mirror rules and SB has less
> c) Both have the same number of mirror rules
>
> All 3 cases are handled and I don't see an issue here. If it had then 
> we would have shown problems in the
> bulk tests right?


I think most of bulk tests (if not all) where attaching different 
mirrors to DIFFERENT ports, while the scenario I'm talking about is when 
different mirrors are attached to the SAME port. In this case, both 
removed and added attachments for the same port may be combined into a 
single transaction.


>
> To double check I tried below adding below bulk test. And don't see 
> any issue.
> Initially I have ls1-lp1 attached to mirror0, mirror1 and mirror3
> And nothing attached to mirror2..
> So the pb will have 3 mirror rules.
> Next as a bulk update I detach 2 (mirror0 and mirror3) and attach 1 
> different mirror mirror2.
> Note mirror1 attachment still remains as its  unchanged
>
> So the pb will have 2 mirror rules now.. (mirror1 and mirror2)
> And this is working fine. I can add this as well to the bulk test maybe.
> (Although we have covered 'more detach and less attach' before this 
> it was NOT with the same port binding.
>   The above one I added in this reply is with THE SAME port binding 
> but it is 'more attach and less detach'
> So, in that way it might be a different case)
> And it is working fine. So hope that clears your concerns regarding 
> this part of the code.
>
> check as hv1 ovn-appctl -t ovn-controller debug/pause
> check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
> check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
> check as hv1 ovn-appctl -t ovn-controller debug/resume
>
> check ovn-nbctl --wait=hv sync
>
> as hv1
> new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
>
> AT_CHECK([test "$origA" = "$new1"], [0], [])
>
> AT_CHECK([test "$origA" = "$new2"], [0], [])
>
> OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
> OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])


Yes this seems correct. If you could add the test cases in the suite, 
that would be nice because it would definitely prove that I am wrong. 
(Plus it would be easier for me to play with it locally and understand 
why I'm wrong.) Thanks.


>
> Thanks  & Regards,
> Abhiram R N
>
>>     +            }
>>     +        }
>>     +
>>     +        struct shash_node *node, *next;
>>     +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>     +            shash_delete(&nb_mirror_rules, node);
>>     +        }
>>     +        shash_destroy(&nb_mirror_rules);
>>     +
>>     +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
>>     +        /* Needs addition in SB */
>>     +        do_sb_mirror_addition(input_data, op);
>     vice versa here: it will only add new rules but not remove
>     obsolete rules
>>     +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
>>     +        /*
>>     +         * Check if its the same mirrors on both SB and NB DBs
>>     +         * If not update accordingly.
>>     +         */
>>     +        bool needs_sb_addition = false;
>>     +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
>>     +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>     +            shash_add(&nb_mirror_rules,
>>     +                                 op->nbsp->mirror_rules[i]->name,
>>     +                                 op->nbsp->mirror_rules[i]);
>>     +        }
>>     +
>>     +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>>     +            if (!shash_find(&nb_mirror_rules,
>>     +                           op->sb->mirror_rules[i]->name)) {
>>     +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>>     +                                                 op->sb->mirror_rules[i]);
>>     +                    needs_sb_addition = true;
>>     +            }
>>     +        }
>>     +
>>     +        struct shash_node *node, *next;
>>     +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>>     +            shash_delete(&nb_mirror_rules, node);
>>     +        }
>>     +        shash_destroy(&nb_mirror_rules);
>>     +
>>     +        if (needs_sb_addition) {
>>     +            do_sb_mirror_addition(input_data, op);
>>     +        }
>>     +    }
>>     +}
>>     +
>>       static void
>>       ovn_port_update_sbrec(struct northd_input *input_data,
>>                             struct ovsdb_idl_txn *ovnsb_txn,
>>     @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>>               }
>>               sbrec_port_binding_set_external_ids(op->sb, &ids);
>>               smap_destroy(&ids);
>>     +
>>     +        if (!op->nbsp->n_mirror_rules) {
>>     +            /* Nothing is set. Clear mirror_rules from pb. */
>>     +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>>     +        } else {
>>     +            /* Check if SB DB update needed */
>>     +            sbrec_port_binding_update_mirror_rules(input_data, op);
>>     +        }
>>     +
>>           }
>>           if (op->tunnel_key != op->sb->tunnel_key) {
>>               sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>>     @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
>>           shash_destroy(&sb_meters);
>>       }
>>       
>>     +static bool
>>     +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>>     +                  const struct sbrec_mirror *sb_mirror)
>>     +{
>>     +
>>     +    if (nb_mirror->index != sb_mirror->index) {
>>     +        return true;
>>     +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>>     +        return true;
>>     +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>>     +        return true;
>>     +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>>     +        return true;
>>     +    }
>>     +
>>     +    return false;
>>     +}
>>     +
>>     +static void
>>     +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>>     +                             const char *mirror_name,
>>     +                             const struct nbrec_mirror *nb_mirror,
>>     +                             struct shash *sb_mirrors,
>>     +                             struct sset *used_sb_mirrors)
>>     +{
>>     +    const struct sbrec_mirror *sb_mirror;
>>     +    bool new_sb_mirror = false;
>>     +
>>     +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>>     +    if (!sb_mirror) {
>>     +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>>     +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>>     +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>>     +        new_sb_mirror = true;
>>     +    }
>>     +    sset_add(used_sb_mirrors, mirror_name);
>>     +
>>     +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
>>     +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>>     +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>>     +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>>     +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>>     +    }
>>     +}
>>     +
>>     +static void
>>     +sync_mirrors(struct northd_input *input_data,
>>     +            struct ovsdb_idl_txn *ovnsb_txn)
>>     +{
>>     +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>>     +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
>>     +
>>     +    const struct sbrec_mirror *sb_mirror;
>>     +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
>>     +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>>     +    }
>>     +
>>     +    const struct nbrec_mirror *nb_mirror;
>>     +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
>>     +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
>>     +                                     &sb_mirrors, &used_sb_mirrors);
>>     +    }
>>     +
>>     +    const char *used_mirror;
>>     +    const char *used_mirror_next;
>>     +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
>>     +        shash_find_and_delete(&sb_mirrors, used_mirror);
>>     +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
>>     +    }
>>     +    sset_destroy(&used_sb_mirrors);
>>     +
>>     +    struct shash_node *node, *next;
>>     +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>>     +        sbrec_mirror_delete(node->data);
>>     +        shash_delete(&sb_mirrors, node);
>>     +    }
>>     +    shash_destroy(&sb_mirrors);
>>     +}
>>     +
>>       /*
>>        * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>>        * and Southbound db.
>>     @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
>>           sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>>           sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>>           sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>>     +    sync_mirrors(input_data, ovnsb_txn);
>>           sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>>           cleanup_stale_fdb_entries(input_data, &data->datapaths);
>>           stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>>     diff --git a/northd/northd.h b/northd/northd.h
>>     index da90e2815..17a62ea10 100644
>>     --- a/northd/northd.h
>>     +++ b/northd/northd.h
>>     @@ -36,6 +36,7 @@ struct northd_input {
>>           const struct nbrec_acl_table *nbrec_acl_table;
>>           const struct nbrec_static_mac_binding_table
>>               *nbrec_static_mac_binding_table;
>>     +    const struct nbrec_mirror_table *nbrec_mirror_table;
>>       
>>           /* Southbound table references */
>>           const struct sbrec_sb_global_table *sbrec_sb_global_table;
>>     @@ -55,6 +56,7 @@ struct northd_input {
>>           const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>>           const struct sbrec_static_mac_binding_table
>>               *sbrec_static_mac_binding_table;
>>     +    const struct sbrec_mirror_table *sbrec_mirror_table;
>>       
>>           /* Indexes */
>>           struct ovsdb_idl_index *sbrec_chassis_by_name;
>>     diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>>     index 174364c8b..01de55222 100644
>>     --- a/ovn-nb.ovsschema
>>     +++ b/ovn-nb.ovsschema
>>     @@ -1,7 +1,7 @@
>>       {
>>           "name": "OVN_Northbound",
>>     -    "version": "6.3.0",
>>     -    "cksum": "4042813038 31869",
>>     +    "version": "6.4.0",
>>     +    "cksum": "589874483 33352",
>>           "tables": {
>>               "NB_Global": {
>>                   "columns": {
>>     @@ -132,6 +132,11 @@
>>                                                   "refType": "weak"},
>>                                        "min": 0,
>>                                        "max": 1}},
>>     +                "mirror_rules": {"type": {"key": {"type": "uuid",
>>     +                                          "refTable": "Mirror",
>>     +                                          "refType": "weak"},
>>     +                                  "min": 0,
>>     +                                  "max": "unlimited"}},
>>                       "ha_chassis_group": {
>>                           "type": {"key": {"type": "uuid",
>>                                            "refTable": "HA_Chassis_Group",
>>     @@ -301,6 +306,28 @@
>>                           "type": {"key": "string", "value": "string",
>>                                    "min": 0, "max": "unlimited"}}},
>>                   "isRoot": false},
>>     +        "Mirror": {
>>     +            "columns": {
>>     +                "name": {"type": "string"},
>>     +                "filter": {"type": {"key": {"type": "string",
>>     +                                            "enum": ["set", ["from-lport",
>>     +                                                             "to-lport",
>>     +                                                             "both"]]}}},
>>     +                "sink":{"type": "string"},
>>     +                "type": {"type": {"key": {"type": "string",
>>     +                                            "enum": ["set", ["gre",
>>     +                                                             "erspan"]]}}},
>>     +                "index": {"type": "integer"},
>>     +                "src": {"type": {"key": {"type": "uuid",
>>     +                                           "refTable": "Logical_Switch_Port",
>>     +                                           "refType": "weak"},
>>     +                                   "min": 0,
>>     +                                   "max": "unlimited"}},
>>     +                "external_ids": {
>>     +                    "type": {"key": "string", "value": "string",
>>     +                             "min": 0, "max": "unlimited"}}},
>>     +            "indexes": [["name"]],
>>     +            "isRoot": true},
>>               "Meter": {
>>                   "columns": {
>>                       "name": {"type": "string"},
>>     diff --git a/ovn-nb.xml b/ovn-nb.xml
>>     index f41e9d7c0..d8730c8fc 100644
>>     --- a/ovn-nb.xml
>>     +++ b/ovn-nb.xml
>>     @@ -1554,6 +1554,11 @@
>>             </column>
>>           </group>
>>       
>>     +    <column name="mirror_rules">
>>     +        Mirror rules that apply to logical switch port which is the source.
>>     +        Please see the <ref table="Mirror"/> table.
>>     +    </column>
>>     +
>>           <column name="ha_chassis_group">
>>             References a row in the OVN Northbound database's
>>             <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>>     @@ -2491,6 +2496,64 @@
>>           </column>
>>         </table>
>>       
>>     +  <table name="Mirror" title="Mirror Entry">
>>     +    <p>
>>     +      Each row in this table represents one Mirror that can be used for
>>     +      port mirroring. These Mirrors are referenced by the
>>     +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
>>     +      the <ref table="Logical_Switch_Port"/> table.
>>     +    </p>
>>     +
>>     +    <column name="name">
>>     +      <p>
>>     +        Represents the name of the mirror.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="filter">
>>     +      <p>
>>     +        The value of this field represents selection criteria of the mirror.
>>     +        Supported values for filter to-lport / from-lport / both
>>     +        to-lport - to mirror packets coming into logical port
>>     +        from-lport - to mirror packets going out of logical port
>>     +        both - to mirror packets coming into and going out of logical port.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="sink">
>>     +      <p>
>>     +        The value of this field represents the destination/sink of the mirror.
>>     +        The value it takes is an IP address of the sink port.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="type">
>>     +      <p>
>>     +        The value of this field represents the type of the tunnel used for
>>     +        sending the mirrored packets. Supported Tunnel types gre and erspan
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="index">
>>     +      <p>
>>     +        The value of this field represents the tunnel ID. Depending on the
>>     +        tunnel type configured, GRE key value if type GRE and erspan_idx value
>>     +        if ERSPAN
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="src">
>>     +      <p>
>>     +        The value of this field represents a list of source ports for the
>>     +        mirror. Please see the <ref table="Logical_Switch_Port"/> table.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="external_ids">
>>     +      See <em>External IDs</em> at the beginning of this document.
>>     +    </column>
>>     +  </table>
>>     +
>>         <table name="Meter" title="Meter entry">
>>           <p>
>>             Each row in this table represents a meter that can be used for QoS or
>>     diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>>     index 576ebbdeb..b83134416 100644
>>     --- a/ovn-sb.ovsschema
>>     +++ b/ovn-sb.ovsschema
>>     @@ -1,7 +1,7 @@
>>       {
>>           "name": "OVN_Southbound",
>>     -    "version": "20.25.0",
>>     -    "cksum": "53184112 28845",
>>     +    "version": "20.26.0",
>>     +    "cksum": "2344151793 30004",
>>           "tables": {
>>               "SB_Global": {
>>                   "columns": {
>>     @@ -142,6 +142,23 @@
>>                   "indexes": [["datapath", "tunnel_key"],
>>                               ["datapath", "name"]],
>>                   "isRoot": true},
>>     +        "Mirror": {
>>     +            "columns": {
>>     +                "name": {"type": "string"},
>>     +                "filter": {"type": {"key": {"type": "string",
>>     +                                            "enum": ["set",
>>     +                                                     ["from-lport",
>>     +                                                      "to-lport","both"]]}}},
>>     +                "sink":{"type": "string"},
>>     +                "type": {"type": {"key": {"type": "string",
>>     +                                            "enum": ["set",
>>     +                                                     ["gre", "erspan"]]}}},
>>     +                "index": {"type": "integer"},
>>     +                "external_ids": {
>>     +                    "type": {"key": "string", "value": "string",
>>     +                             "min": 0, "max": "unlimited"}}},
>>     +            "indexes": [["name"]],
>>     +            "isRoot": true},
>>               "Meter": {
>>                   "columns": {
>>                       "name": {"type": "string"},
>>     @@ -230,6 +247,11 @@
>>                                                             "refTable": "Encap",
>>                                                             "refType": "weak"},
>>                                           "min": 0, "max": "unlimited"}},
>>     +                "mirror_rules": {"type": {"key": {"type": "uuid",
>>     +                                          "refTable": "Mirror",
>>     +                                          "refType": "weak"},
>>     +                                  "min": 0,
>>     +                                  "max": "unlimited"}},
>>                       "mac": {"type": {"key": "string",
>>                                        "min": 0,
>>                                        "max": "unlimited"}},
>>     diff --git a/ovn-sb.xml b/ovn-sb.xml
>>     index 315d60853..05c0db6b4 100644
>>     --- a/ovn-sb.xml
>>     +++ b/ovn-sb.xml
>>     @@ -2742,6 +2742,51 @@ tcp.flags = RST;
>>           </column>
>>         </table>
>>       
>>     +  <table name="Mirror" title="Mirror Entry">
>>     +    <p>
>>     +      Each row in this table represents one Mirror that can be used for
>>     +      port mirroring. These Mirrors are referenced by the
>>     +      <ref column="mirror_rules" table="Port_Binding"/> column in
>>     +      the <ref table="Port_Binding"/> table.
>>     +    </p>
>>     +
>>     +    <column name="name">
>>     +      <p>
>>     +        Represents the name of the mirror.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="filter">
>>     +      <p>
>>     +        The value of this field represents selection criteria of the mirror.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="sink">
>>     +      <p>
>>     +        The value of this field represents the destination/sink of the mirror.
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="type">
>>     +      <p>
>>     +        The value of this field represents the type of the tunnel used for
>>     +        sending the mirrored packets
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="index">
>>     +      <p>
>>     +        The value of this field represents the key/idx depending on the
>>     +        tunnel type configured
>>     +      </p>
>>     +    </column>
>>     +
>>     +    <column name="external_ids">
>>     +      See <em>External IDs</em> at the beginning of this document.
>>     +    </column>
>>     +  </table>
>>     +
>>         <table name="Meter" title="Meter entry">
>>           <p>
>>             Each row in this table represents a meter that can be used for QoS or
>>     @@ -3244,6 +3289,11 @@ tcp.flags = RST;
>>             </column>
>>           </group>
>>       
>>     +    <column name="mirror_rules">
>>     +        Mirror rules that apply to the port binding.
>>     +        Please see the <ref table="Mirror"/> table.
>>     +    </column>
>>     +
>>           <group title="Patch Options">
>>             <p>
>>               These options apply to logical ports with <ref column="type"/> of
>>     diff --git a/tests/ovn-nbctl.at  <http://ovn-nbctl.at>  b/tests/ovn-nbctl.at  <http://ovn-nbctl.at>
>>     index 4d480e357..d79f9d929 100644
>>     --- a/tests/ovn-nbctl.at  <http://ovn-nbctl.at>
>>     +++ b/tests/ovn-nbctl.at  <http://ovn-nbctl.at>
>>     @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>>       
>>       dnl ---------------------------------------------------------------------
>>       
>>     +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>>     +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
>>     +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>>     +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
>>     +AT_CHECK([ovn-nbctl ls-add sw0])
>>     +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>>     +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>>     +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>>     +
>>     +dnl Add duplicate mirror name
>>     +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
>>     +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>>     +
>>     +dnl Attach invalid source port to mirror
>>     +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
>>     +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>>     +
>>     +dnl Attach source port to invalid mirror
>>     +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], [stderr])
>>     +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
>>     +
>>     +dnl Attach source port to mirror
>>     +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>>     +
>>     +dnl Attach one more source port to mirror
>>     +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>>     +
>>     +dnl Verify if multiple ports are attached to the same mirror properly
>>     +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>     +mirror1:
>>     +  Type     :  gre
>>     +  Sink     :  10.10.10.1
>>     +  Filter   :  from-lport
>>     +  Index/Key:  0
>>     +  Sources  :  None attached
>>     +mirror2:
>>     +  Type     :  erspan
>>     +  Sink     :  10.10.10.2
>>     +  Filter   :  both
>>     +  Index/Key:  1
>>     +  Sources  :  None attached
>>     +mirror3:
>>     +  Type     :  gre
>>     +  Sink     :  10.10.10.3
>>     +  Filter   :  to-lport
>>     +  Index/Key:  2
>>     +  Sources  :  sw0-port1  sw0-port3
>>     +])
>>     +
>>     +dnl Detach one source port from mirror
>>     +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>>     +
>>     +dnl Verify if detach source port from mirror happens properly
>>     +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>     +mirror1:
>>     +  Type     :  gre
>>     +  Sink     :  10.10.10.1
>>     +  Filter   :  from-lport
>>     +  Index/Key:  0
>>     +  Sources  :  None attached
>>     +mirror2:
>>     +  Type     :  erspan
>>     +  Sink     :  10.10.10.2
>>     +  Filter   :  both
>>     +  Index/Key:  1
>>     +  Sources  :  None attached
>>     +mirror3:
>>     +  Type     :  gre
>>     +  Sink     :  10.10.10.3
>>     +  Filter   :  to-lport
>>     +  Index/Key:  2
>>     +  Sources  :  sw0-port1
>>     +])
>>     +
>>     +dnl Delete a single mirror which has source attached.
>>     +AT_CHECK([ovn-nbctl mirror-del mirror3])
>>     +
>>     +dnl Check if the detach happened from source properly
>>     +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules |  cut -b 3], [0], [dnl
>>     +
>>     +])
>>     +
>>     +dnl Check if the mirror deleted properly
>>     +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>     +mirror1:
>>     +  Type     :  gre
>>     +  Sink     :  10.10.10.1
>>     +  Filter   :  from-lport
>>     +  Index/Key:  0
>>     +  Sources  :  None attached
>>     +mirror2:
>>     +  Type     :  erspan
>>     +  Sink     :  10.10.10.2
>>     +  Filter   :  both
>>     +  Index/Key:  1
>>     +  Sources  :  None attached
>>     +])
>>     +
>>     +dnl Delete another mirror
>>     +AT_CHECK([ovn-nbctl mirror-del mirror2])
>>     +
>>     +dnl Update the Sink address
>>     +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>>     +
>>     +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>     +mirror1:
>>     +  Type     :  gre
>>     +  Sink     :  192.168.1.13
>>     +  Filter   :  from-lport
>>     +  Index/Key:  0
>>     +  Sources  :  None attached
>>     +])
>>     +
>>     +dnl Delete all mirrors
>>     +AT_CHECK([ovn-nbctl mirror-del])
>>     +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>     +])])
>>     +
>>     +dnl ---------------------------------------------------------------------
>>     +
>>       OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>>       AT_CHECK([ovn-nbctl lr-add lr0])
>>       AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
>>     diff --git a/tests/ovn-northd.at  <http://ovn-northd.at>  b/tests/ovn-northd.at  <http://ovn-northd.at>
>>     index 4f399eccb..4e6c268e4 100644
>>     --- a/tests/ovn-northd.at  <http://ovn-northd.at>
>>     +++ b/tests/ovn-northd.at  <http://ovn-northd.at>
>>     @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>>       AT_CLEANUP
>>       ])
>>       
>>     +OVN_FOR_EACH_NORTHD_NO_HV([
>>     +AT_SETUP([Check NB-SB mirrors sync])
>>     +AT_KEYWORDS([mirrors])
>>     +ovn_start
>>     +
>>     +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>     +"10.10.10.2"
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>     +erspan
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>     +0
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>     +both
>>     +])
>>     +
>>     +check ovn-nbctl --wait=sb \
>>     +    -- set mirror . sink=192.168.1.13
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>     +"192.168.1.13"
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>     +erspan
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>     +0
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>     +both
>>     +])
>>     +
>>     +check ovn-nbctl --wait=sb \
>>     +    -- set mirror . type=gre
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>     +gre
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>     +"192.168.1.13"
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>     +0
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>     +both
>>     +])
>>     +
>>     +check ovn-nbctl --wait=sb \
>>     +    -- set mirror . index=12
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>     +12
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>     +gre
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>     +"192.168.1.13"
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>     +both
>>     +])
>>     +
>>     +check ovn-nbctl --wait=sb \
>>     +    -- set mirror . filter=to-lport
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>>     +to-lport
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>>     +12
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>>     +gre
>>     +])
>>     +
>>     +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>>     +"192.168.1.13"
>>     +])
>>     +
>>     +AT_CLEANUP
>>     +])
>>     +
>>       OVN_FOR_EACH_NORTHD_NO_HV([
>>       AT_SETUP([ACL skip hints for stateless config])
>>       AT_KEYWORDS([acl])
>>     diff --git a/tests/ovn.at  <http://ovn.at>  b/tests/ovn.at  <http://ovn.at>
>>     index f8b8db4df..cd5527ea1 100644
>>     --- a/tests/ovn.at  <http://ovn.at>
>>     +++ b/tests/ovn.at  <http://ovn.at>
>>     @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
>>       AT_CLEANUP
>>       ])
>>       
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror])
>>     +AT_KEYWORDS([Mirror])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1 \
>>     +    options:tx_pcap=hv1/vif1-tx.pcap \
>>     +    options:rxq_pcap=hv1/vif1-rx.pcap \
>>     +    ofport-request=1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1 \
>>     +    options:tx_pcap=hv1/vif2-tx.pcap \
>>     +    options:rxq_pcap=hv1/vif2-rx.pcap \
>>     +    ofport-request=1
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +ovn-nbctl dump-flows > sbflows
>>     +AT_CAPTURE_FILE([sbflows])
>>     +
>>     +for i in 1 2; do
>>     +    : > vif$i.expected
>>     +done
>>     +
>>     +net_add n2
>>     +
>>     +sim_add hv2
>>     +as hv2
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
>>     +ovn_attach n2 br-phys 192.168.1.12
>>     +
>>     +OVN_POPULATE_ARP
>>     +
>>     +as hv1
>>     +
>>     +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
>>     +#
>>     +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
>>     +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
>>     +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
>>     +# provided, then it should be the ip and icmp checksums of the packet
>>     +# responded; otherwise, no reply is expected.
>>     +# In the absence of an ip checksum calculation helpers, this relies
>>     +# on the caller to provide the checksums for the ip and icmp headers.
>>     +# XXX This should be more systematic.
>>     +#
>>     +# INPORT is an lport number, e.g. 11 for vif11.
>>     +# ETH_SRC and ETH_DST are each 12 hex digits.
>>     +# IPV4_SRC and IPV4_DST are each 8 hex digits.
>>     +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
>>     +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
>>     +# ENCAP_TYPE - gre or erspan
>>     +# FILTER - Mirror Filter - to-lport / from-lport
>>     +test_ipv4_icmp_request() {
>>     +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
>>     +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
>>     +    shift; shift; shift; shift; shift; shift; shift
>>     +    shift; shift; shift; shift;
>>     +
>>     +    # Use ttl to exercise section 4.2.2.9 of RFC1812
>>     +    local ip_ttl=02
>>     +    local icmp_id=5fbf
>>     +    local icmp_seq=0001
>>     +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
>>     +    local icmp_type_code_request=0800
>>     +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>     +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
>>     +
>>     +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
>>     +
>>     +    # Expect to receive the reply, if any. In same port where packet was sent.
>>     +    # Note: src and dst fields are expected to be reversed.
>>     +    local icmp_type_code_response=0000
>>     +    local reply_icmp_ttl=fe
>>     +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
>>     +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
>>     +    echo $reply >> vif$inport.expected
>>     +    local remote_mac=000000020200
>>     +    local local_mac=000000010200
>>     +    if test ${mirror_encap_type} = "gre" ; then
>>     +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
>>     +        if test ${mirror_filter} = "to-lport" ; then
>>     +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
>>     +        elif test ${mirror_filter} = "from-lport" ; then
>>     +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
>>     +        fi
>>     +    elif test ${mirror_encap_type} = "erspan" ; then
>>     +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
>>     +        local erspan_seq0=100088be000000001000000000000000
>>     +        local erspan_seq1=100088be000000011000000000000000
>>     +        if test ${mirror_filter} = "to-lport" ; then
>>     +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
>>     +        elif test ${mirror_filter} = "from-lport" ; then
>>     +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
>>     +        fi
>>     +    fi
>>     +    echo $mirror >> br-phys_n1.expected
>>     +
>>     +}
>>     +
>>     +# Set IPs
>>     +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>>     +l1_ip=$(ip_to_hex 192 168 1 2)
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +
>>     +# Send ping packet and check for mirrored packet of the reply
>>     +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
>>     +
>>     +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>>     +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>     +
>>     +as hv1 reset_pcap_file vif1 hv1/vif1
>>     +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>     +rm -f br-phys_n1.expected
>>     +rm -f vif1.expected
>>     +
>>     +check ovn-nbctl set mirror . type=erspan
>>     +
>>     +# Send ping packet and check for mirrored packet of the reply
>>     +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
>>     +
>>     +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>>     +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>     +
>>     +as hv1 reset_pcap_file vif1 hv1/vif1
>>     +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>     +rm -f br-phys_n1.expected
>>     +rm -f vif1.expected
>>     +
>>     +check ovn-nbctl set mirror . filter=from-lport
>>     +
>>     +# Send ping packet and check for mirrored packet of the request
>>     +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
>>     +
>>     +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>>     +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>     +
>>     +as hv1 reset_pcap_file vif1 hv1/vif1
>>     +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
>>     +rm -f br-phys_n1.expected
>>     +rm -f vif1.expected
>>     +
>>     +check ovn-nbctl set mirror . type=gre
>>     +
>>     +# Send ping packet and check for mirrored packet of the request
>>     +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
>>     +
>>     +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>>     +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
>>     +
>>     +echo "---------OVN NB Mirror-----"
>>     +ovn-nbctl mirror-list
>>     +
>>     +echo "---------OVS Mirror----"
>>     +ovs-vsctl list Mirror
>>     +
>>     +echo "-----------------------"
>>     +
>>     +echo "Verifying Mirror deletion in OVS"
>>     +# Set vif1 iface-id such that OVN releases port binding
>>     +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>>     +])
>>     +
>>     +# Set vif1 iface-id back to ls1-lp1
>>     +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
>>     +
>>     +# Delete vif1 so that OVN releases port binding
>>     +check ovs-vsctl del-port br-int vif1
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>>     +
>>     +OVN_CLEANUP([hv1], [hv2])
>>     +AT_CLEANUP
>>     +])
>>     +
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror test bulk swap attachments])
>>     +AT_KEYWORDS([Mirror test bulk swap attachments])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls1-lp2 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>     +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +# Create logical port ls2-lp2 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>     +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif3 -- \
>>     +    set interface vif3 external-ids:iface-id=ls1-lp2
>>     +
>>     +ovs-vsctl -- add-port br-int vif4 -- \
>>     +    set interface vif4 external-ids:iface-id=ls2-lp2
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>     +
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +# Equal detaches and attaches
>>     +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>     +
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>     +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>     +
>>     +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>     +
>>     +OVN_CLEANUP([hv1])
>>     +AT_CLEANUP
>>     +])
>>     +
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror test bulk attach multiple])
>>     +AT_KEYWORDS([Mirror test bulk attach multiple])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls1-lp2 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>     +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +# Create logical port ls2-lp2 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>     +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif3 -- \
>>     +    set interface vif3 external-ids:iface-id=ls1-lp2
>>     +
>>     +ovs-vsctl -- add-port br-int vif4 -- \
>>     +    set interface vif4 external-ids:iface-id=ls2-lp2
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>     +
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +check ovn-nbctl mirror-del
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +
>>     +# Attaches multiple mirrors
>>     +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +AT_CHECK([test "$orig1" = "$new1"], [0], [])
>>     +
>>     +AT_CHECK([test "$orig2" = "$new2"], [0], [])
>>     +
>>     +OVN_CLEANUP([hv1])
>>     +AT_CLEANUP
>>     +])
>>     +
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror test bulk more detach and less attach])
>>     +AT_KEYWORDS([Mirror test bulk more detach and less attach])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls1-lp2 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>     +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +# Create logical port ls2-lp2 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>     +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif3 -- \
>>     +    set interface vif3 external-ids:iface-id=ls1-lp2
>>     +
>>     +ovs-vsctl -- add-port br-int vif4 -- \
>>     +    set interface vif4 external-ids:iface-id=ls2-lp2
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl --wait=hv sync
>>     +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl --wait=hv sync
>>     +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +check ovn-nbctl mirror-del
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +# Detaches more than attaches
>>     +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +AT_CHECK([test "$origA" = "$new1"], [0], [])
>>     +
>>     +AT_CHECK([test "$origB" = "$new2"], [0], [])
>>     +
>>     +OVN_CLEANUP([hv1])
>>     +AT_CLEANUP
>>     +])
>>     +
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror test bulk attach more than detach])
>>     +AT_KEYWORDS([Mirror test bulk attach more than detach])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls1-lp2 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>     +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +# Create logical port ls2-lp2 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>     +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif3 -- \
>>     +    set interface vif3 external-ids:iface-id=ls1-lp2
>>     +
>>     +ovs-vsctl -- add-port br-int vif4 -- \
>>     +    set interface vif4 external-ids:iface-id=ls2-lp2
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>     +
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +# Attaches more than detaches
>>     +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
>>     +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +as hv1
>>     +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
>>     +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
>>     +
>>     +AT_CHECK([test "$orig1" = "$new2"], [0], [])
>>     +
>>     +AT_CHECK([test "$orig2" = "$new1"], [0], [])
>>     +
>>     +OVN_CLEANUP([hv1])
>>     +AT_CLEANUP
>>     +])
>>     +
>>     +OVN_FOR_EACH_NORTHD([
>>     +AT_SETUP([Mirror test bulk detach multiple])
>>     +AT_KEYWORDS([Mirror test bulk detach multiple])
>>     +ovn_start
>>     +
>>     +# Logical network:
>>     +# One LR - R1 has switch ls1 (191.168.1.0/24  <http://191.168.1.0/24>) connected to it,
>>     +# and has switch ls2 (172.16.1.0/24  <http://172.16.1.0/24>) connected to it.
>>     +
>>     +ovn-nbctl lr-add R1
>>     +
>>     +ovn-nbctl ls-add ls1
>>     +ovn-nbctl ls-add ls2
>>     +
>>     +# Connect ls1 to R1
>>     +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1192.168.1.1/24 +ovn-nbctl  <http://192.168.1.1/24+ovn-nbctl>  lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
>>     +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
>>     +
>>     +# Connect ls2 to R1
>>     +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2172.16.1.1/24 +ovn-nbctl  <http://172.16.1.1/24+ovn-nbctl>  lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
>>     +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
>>     +
>>     +# Create logical port ls1-lp1 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp1 \
>>     +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
>>     +
>>     +# Create logical port ls1-lp2 in ls1
>>     +ovn-nbctl lsp-add ls1 ls1-lp2 \
>>     +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
>>     +
>>     +# Create logical port ls2-lp1 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp1 \
>>     +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
>>     +
>>     +# Create logical port ls2-lp2 in ls2
>>     +ovn-nbctl lsp-add ls2 ls2-lp2 \
>>     +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
>>     +
>>     +ovn-nbctl lsp-add ls1 ln-public
>>     +ovn-nbctl lsp-set-type ln-public localnet
>>     +ovn-nbctl lsp-set-addresses ln-public unknown
>>     +ovn-nbctl lsp-set-options ln-public network_name=public
>>     +
>>     +# Create one hypervisor and create OVS ports corresponding to logical ports.
>>     +net_add n1
>>     +
>>     +sim_add hv1
>>     +as hv1
>>     +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
>>     +ovn_attach n1 br-phys 192.168.1.11
>>     +
>>     +ovs-vsctl -- add-port br-int vif1 -- \
>>     +    set interface vif1 external-ids:iface-id=ls1-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif2 -- \
>>     +    set interface vif2 external-ids:iface-id=ls2-lp1
>>     +
>>     +ovs-vsctl -- add-port br-int vif3 -- \
>>     +    set interface vif3 external-ids:iface-id=ls1-lp2
>>     +
>>     +ovs-vsctl -- add-port br-int vif4 -- \
>>     +    set interface vif4 external-ids:iface-id=ls2-lp2
>>     +
>>     +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
>>     +
>>     +# Allow some time for ovn-northd and ovn-controller to catch up.
>>     +wait_for_ports_up
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
>>     +
>>     +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
>>     +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
>>     +
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +# Detaches all
>>     +check as hv1 ovn-appctl -t ovn-controller debug/pause
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
>>     +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
>>     +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
>>     +check as hv1 ovn-appctl -t ovn-controller debug/resume
>>     +check ovn-nbctl --wait=hv sync
>>     +
>>     +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
>>     +
>>     +OVN_CLEANUP([hv1])
>>     +AT_CLEANUP
>>     +])
>>       
>>       OVN_FOR_EACH_NORTHD([
>>       AT_SETUP([Port Groups])
>>     diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>>     index 811468dc6..af2e61435 100644
>>     --- a/utilities/ovn-nbctl.c
>>     +++ b/utilities/ovn-nbctl.c
>>     @@ -271,6 +271,19 @@ QoS commands:\n\
>>                                   remove QoS rules from SWITCH\n\
>>         qos-list SWITCH           print QoS rules for SWITCH\n\
>>       \n\
>>     +Mirror commands:\n\
>>     +  mirror-add NAME TYPE INDEX FILTER IP\n\
>>     +                            add a mirror with given name\n\
>>     +                            specify TYPE 'gre' or 'erspan'\n\
>>     +                            specify the tunnel INDEX value\n\
>>     +                                (indicates key if GRE\n\
>>     +                                 erpsan_idx if ERSPAN)\n\
>>     +                            specify FILTER for mirroring selection\n\
>>     +                                'to-lport' / 'from-lport' / 'both'\n\
>>     +                            specify Sink / Destination i.e. Remote IP\n\
>>     +  mirror-del [NAME]         remove mirrors\n\
>>     +  mirror-list               print mirrors\n\
>>     +\n\
>>       Meter commands:\n\
>>         [--fair]\n\
>>         meter-add NAME ACTION RATE UNIT [BURST]\n\
>>     @@ -311,6 +324,8 @@ Logical switch port commands:\n\
>>                                   set dhcpv6 options for PORT\n\
>>         lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>>         lsp-get-ls PORT           get the logical switch which the port belongs to\n\
>>     +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
>>     +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
>>       \n\
>>       Forwarding group commands:\n\
>>         [--liveness]\n\
>>     @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>>           ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>>       }
>>       
>>     +static void
>>     +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>>     +{
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl,
>>     +                         &nbrec_logical_switch_port_col_mirror_rules);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>     +}
>>     +
>>     +static int
>>     +mirror_cmp(const void *mirror1_, const void *mirror2_)
>>     +{
>>     +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>>     +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>>     +
>>     +    const struct nbrec_mirror *mirror1 = *mirror_1;
>>     +    const struct nbrec_mirror *mirror2 = *mirror_2;
>>     +
>>     +    return strcmp(mirror1->name,mirror2->name);
>>     +}
>>     +
>>     +static char * OVS_WARN_UNUSED_RESULT
>>     +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>>     +                    bool must_exist,
>>     +                    const struct nbrec_mirror **mirror_p)
>>     +{
>>     +    const struct nbrec_mirror *mirror = NULL;
>>     +    *mirror_p = NULL;
>>     +
>>     +    struct uuid mirror_uuid;
>>     +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>>     +    if (is_uuid) {
>>     +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
>>     +    }
>>     +
>>     +    if (!mirror) {
>>     +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>     +            if (!strcmp(mirror->name, id)) {
>>     +                break;
>>     +            }
>>     +        }
>>     +    }
>>     +
>>     +    if (!mirror && must_exist) {
>>     +        return xasprintf("%s: mirror %s not found",
>>     +                         id, is_uuid ? "UUID" : "name");
>>     +    }
>>     +
>>     +    *mirror_p = mirror;
>>     +    return NULL;
>>     +}
>>     +
>>     +static void
>>     +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>>     +{
>>     +    const char *port = ctx->argv[1];
>>     +    const char *mirror_name = ctx->argv[2];
>>     +    const struct nbrec_logical_switch_port *lsp = NULL;
>>     +    const struct nbrec_mirror *mirror;
>>     +
>>     +    char *error;
>>     +
>>     +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +
>>     +    /*check if a mirror rule actually exists on that name or not*/
>>     +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +    /* Check if same mirror rule already exists for the lsp */
>>     +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>>     +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>>     +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
>>     +            if (!may_exist) {
>>     +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
>>     +                          ctx->argv[1]);
>>     +                return;
>>     +            }
>>     +            return;
>>     +        }
>>     +    }
>>     +
>>     +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
>>     +    nbrec_mirror_update_src_addvalue(mirror,lsp);
>>     +
>>     +}
>>     +
>>     +static void
>>     +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>>     +{
>>     +    const char *port = ctx->argv[1];
>>     +    const char *mirror_name = ctx->argv[2];
>>     +    const struct nbrec_logical_switch_port *lsp = NULL;
>>     +    const struct nbrec_mirror *mirror;
>>     +
>>     +    char *error;
>>     +
>>     +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +
>>     +    /*check if a mirror rule actually exists on that name or not*/
>>     +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
>>     +    nbrec_mirror_update_src_delvalue(mirror,lsp);
>>     +
>>     +}
>>     +
>>       static void
>>       nbctl_lsp_set_type(struct ctl_context *ctx)
>>       {
>>     @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>>           nbrec_ha_chassis_set_priority(ha_chassis, priority);
>>       }
>>       
>>     +static char * OVS_WARN_UNUSED_RESULT
>>     +parse_filter(const char *arg, const char **selection_p)
>>     +{
>>     +    /* Validate selection.  Only require the first letter. */
>>     +    if (arg[0] == 't') {
>>     +        *selection_p = "to-lport";
>>     +    } else if (arg[0] == 'f') {
>>     +        *selection_p = "from-lport";
>>     +    } else if (arg[0] == 'b') {
>>     +        *selection_p = "both";
>>     +    } else {
>>     +        *selection_p = NULL;
>>     +        return xasprintf("%s: selection must be \"to-lport\" or "
>>     +                         "\"from-lport\" or \"both\" ", arg);
>>     +    }
>>     +    return NULL;
>>     +}
>>     +
>>     +static char * OVS_WARN_UNUSED_RESULT
>>     +parse_type(const char *arg, const char **type_p)
>>     +{
>>     +    /* Validate type.  Only require the first letter. */
>>     +    if (arg[0] == 'g') {
>>     +        *type_p = "gre";
>>     +    } else if (arg[0] == 'e') {
>>     +        *type_p = "erspan";
>>     +    } else {
>>     +        *type_p = NULL;
>>     +        return xasprintf("%s: type must be \"gre\" or "
>>     +                         "\"erspan\"", arg);
>>     +    }
>>     +    return NULL;
>>     +}
>>     +
>>     +static void
>>     +nbctl_pre_mirror_add(struct ctl_context *ctx)
>>     +{
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>     +}
>>     +
>>     +static void
>>     +nbctl_mirror_add(struct ctl_context *ctx)
>>     +{
>>     +    const char *filter = NULL;
>>     +    const char *sink_ip = NULL;
>>     +    const char *type = NULL;
>>     +    const char *name = NULL;
>>     +    char *new_sink_ip = NULL;
>>     +    int64_t index;
>>     +    char *error = NULL;
>>     +    const struct nbrec_mirror *mirror_check = NULL;
>>     +
>>     +    /* Mirror Name */
>>     +    name = ctx->argv[1];
>>     +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>>     +        if (!strcmp(mirror_check->name, name)) {
>>     +            ctl_error(ctx, "Mirror with %s name already exists.",
>>     +                      name);
>>     +            return;
>>     +        }
>>     +    }
>>     +
>>     +    /* Tunnel Type - GRE/ERSPAN */
>>     +    error = parse_type(ctx->argv[2], &type);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +    /* tunnel index / GRE key / ERSPAN idx */
>>     +    index = atoi(ctx->argv[3]);
>>     +
>>     +    /* Filter for mirroring */
>>     +    error = parse_filter(ctx->argv[4], &filter);
>>     +    if (error) {
>>     +        ctx->error = error;
>>     +        return;
>>     +    }
>>     +
>>     +    /* Destination / Sink details */
>>     +    sink_ip = ctx->argv[5];
>>     +
>>     +    /* check if it is a valid ip */
>>     +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
>>     +    if (!new_sink_ip) {
>>     +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
>>     +    }
>>     +
>>     +    if (new_sink_ip) {
>>     +        free(new_sink_ip);
>>     +    } else {
>>     +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>>     +        return;
>>     +    }
>>     +
>>     +    /* Create the mirror. */
>>     +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>>     +    nbrec_mirror_set_name(mirror, name);
>>     +    nbrec_mirror_set_index(mirror, index);
>>     +    nbrec_mirror_set_filter(mirror, filter);
>>     +    nbrec_mirror_set_type(mirror, type);
>>     +    nbrec_mirror_set_sink(mirror, sink_ip);
>>     +
>>     +}
>>     +
>>     +static void
>>     +nbctl_pre_mirror_del(struct ctl_context *ctx)
>>     +{
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>     +}
>>     +
>>     +static void
>>     +nbctl_mirror_del(struct ctl_context *ctx)
>>     +{
>>     +    const struct nbrec_mirror *mirror, *next;
>>     +
>>     +    /* If a name is not specified, delete all mirrors. */
>>     +    if (ctx->argc == 1) {
>>     +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>>     +            nbrec_mirror_delete(mirror);
>>     +        }
>>     +        return;
>>     +    }
>>     +
>>     +    /* Remove the matching mirror. */
>>     +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>     +        if (strcmp(ctx->argv[1], mirror->name)) {
>>     +            continue;
>>     +        }
>>     +        nbrec_mirror_delete(mirror);
>>     +        return;
>>     +    }
>>     +}
>>     +
>>     +static void
>>     +nbctl_pre_mirror_list(struct ctl_context *ctx)
>>     +{
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>     +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>>     +}
>>     +
>>     +static void
>>     +nbctl_mirror_list(struct ctl_context *ctx)
>>     +{
>>     +
>>     +    const struct nbrec_mirror **mirrors = NULL;
>>     +    const struct nbrec_mirror *mirror;
>>     +    size_t n_capacity = 0;
>>     +    size_t n_mirrors = 0;
>>     +
>>     +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>>     +        if (n_mirrors == n_capacity) {
>>     +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
>>     +        }
>>     +
>>     +        mirrors[n_mirrors] = mirror;
>>     +        n_mirrors++;
>>     +    }
>>     +
>>     +    if (n_mirrors) {
>>     +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>>     +    }
>>     +
>>     +    for (size_t i = 0; i < n_mirrors; i++) {
>>     +        mirror = mirrors[i];
>>     +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>>     +        /* print all the values */
>>     +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
>>     +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
>>     +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
>>     +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
>>     +                                                (long int) mirror->index);
>>     +        ds_put_cstr(&ctx->output,   "  Sources  :");
>>     +        if (mirror->n_src > 0) {
>>     +            struct svec srcs;
>>     +            const char *src;
>>     +            size_t j;
>>     +            svec_init(&srcs);
>>     +            for (j = 0; j < mirror->n_src; j++) {
>>     +                svec_add(&srcs, mirror->src[j]->name);
>>     +            }
>>     +            svec_sort(&srcs);
>>     +            SVEC_FOR_EACH (j, src, &srcs) {
>>     +                ds_put_format(&ctx->output, "  %s", src);
>>     +            }
>>     +            svec_destroy(&srcs);
>>     +        } else {
>>     +            ds_put_cstr(&ctx->output, "  None attached");
>>     +        }
>>     +        ds_put_cstr(&ctx->output, "\n");
>>     +    }
>>     +
>>     +    free(mirrors);
>>     +}
>>     +
>>       static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>>           [NBREC_TABLE_DHCP_OPTIONS].row_ids
>>           = {{&nbrec_logical_switch_port_col_name, NULL,
>>     @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>>           { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>>             NULL, "", RO },
>>       
>>     +    /* mirror commands. */
>>     +    { "mirror-add", 5, 5,
>>     +      "NAME TYPE INDEX FILTER IP",
>>     +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
>>     +    { "mirror-del", 0, 1, "[NAME]",
>>     +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>>     +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
>>     +      NULL, "", RO },
>>     +
>>           /* meter commands. */
>>           { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>>             nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>>     @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>>             nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>>           { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>>             NULL, "", RO },
>>     +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>>     +      nbctl_lsp_attach_mirror, NULL, "", RW },
>>     +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>>     +      nbctl_lsp_detach_mirror, NULL, "", RW },
>>       
>>           /* forwarding group commands. */
>>           { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
>>     diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>>     index f60dde1b6..3d73e9e25 100644
>>     --- a/utilities/ovn-sbctl.c
>>     +++ b/utilities/ovn-sbctl.c
>>     @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>>           ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>>           ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>>           ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>>     +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>>       
>>           ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>>           ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
>>     @@ -1431,6 +1432,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>>           [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
>>           = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
>>       
>>     +    [SBREC_TABLE_MIRROR].row_ids[0]
>>     +    = {&sbrec_mirror_col_name, NULL, NULL},
>>     +
>>           [SBREC_TABLE_METER].row_ids[0]
>>           = {&sbrec_meter_col_name, NULL, NULL},
>>       
>
Ihar Hrachyshka Nov. 17, 2022, 7:15 p.m. UTC | #11
Dear Abhiram,

today during the OVN IRC meeting we discussed this issue with vswitchd
frozen on incorrect (?) mirror setup. We believe it is a legit issue
in OVS. We have to understand what triggers it and whether it's safe
to land OVN support before the OVS issue is resolved. (We'll also need
to understand what happens before we can resolve it, obviously.) We'll
probably need a OVS only reproducer that wouldn't involve OVN API. Do
you think you could produce it in the next few days so that OVS folks
can take a look? This should help with getting the patch to the finish
line. Thanks.

BTW Ilya is on PTO and may not respond in a timely manner, so we
should try to deal with it ourselves.

Thanks again for your help.

On Tue, Nov 15, 2022 at 6:09 PM Ihar Hrachyshka <ihrachys@redhat.com> wrote:
>
> On 11/15/22 5:42 PM, Ihar Hrachyshka wrote:
> > I think there's a problem with the bulk tests added in this patch. I
> > will cover this issue in this email, and I'll send my code review
> > tomorrow as promised, since it's getting late here.
> >
> >
> > When running the whole suite locally, I get the following failures:
> >
> >
> > 401: Mirror test bulk swap attachments -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16425)
> > 402: Mirror test bulk swap attachments -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16425)
> > 403: Mirror test bulk attach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16537)
> > 410: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no ok
> > 412: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no ok
> > 416: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no ok
> > 408: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=no FAILED (ovn.at:16650)
> > 417: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes ok
> > 409: Mirror test bulk more detach and less attach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16650)
> > 411: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> > 418: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no ok
> > 413: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=yes FAILED (ovn.at:16766)
> > 415: Mirror test bulk detach multiple -- ovn-northd --
> > parallelization=yes -- ovn_monitor_all=yes FAILED (ovn.at:16879)
> > 414: Mirror test bulk attach more than detach -- ovn-northd --
> > parallelization=no -- ovn_monitor_all=no FAILED (ovn.at:16766)
> >
> >
> > Note that some of the test case variants passed, and I don't think
> > there's a clear pattern as to which of variants in the test matrix do
> > fail.
> >
> >
> > The error that triggers the failure is during ovs-vswitchd cleanup:
> >
> >
> > ./ovn.at:16425: ovs-appctl --timeout=10 -t ovs-vswitchd exit --cleanup
> > --- /dev/null   2022-11-04 04:09:25.869645998 +0000
> > +++ /home/vagrant/ovn/tests/testsuite.dir/at-groups/401/stderr
> > 2022-11-15 16:31:15.557479369 +0000
> > @@ -0,0 +1,2 @@
> > +2022-11-15T16:31:15Z|00001|fatal_signal|WARN|terminating with signal
> > 14 (Alarm clock)
> > +/home/vagrant/ovn/tests/testsuite.dir/at-groups/401/test-source: line
> > 282: 1033659 Alarm clock             ovs-appctl --timeout=10 -t
> > ovs-vswitchd exit --cleanup
> > ./ovn.at:16425: exit code was 142, expected 0
> >
> >
> > The very last message in ovs-vswitchd log on hv1 is exactly 10 seconds
> > before the alarm clock error:
> >
> >
> > 2022-11-15T16:31:05.280Z|00541|ofproto_dpif_xlate|WARN|over max
> > translation depth 64 on bridge br-int while processing
> > arp,in_port=LOCAL,vlan_tci=0x0000,dl_src=00:00:00:01:02:00,dl_dst=ff:ff:ff:ff:ff:ff,arp_spa=192.168.1.11,arp_tpa=192.168.1.12,arp_op=1,arp_sha=00:00:00:01:02:00,arp_tha=00:00:00:00:00:00
> >
> >
> > I don't see coredumps generated for any of test processes, so it's
> > probably not the case of ovs-vswitchd crashing on exit request.
> >
> >
> > I tried to adjust your test cases to a minimal reproducer and I found
> > that if a test case creates two mirrors, both of to-lport type, then
> > ovs-vswitchd freezes (?) - f.e. it no longer responds to appctl
> > requests, nor it handles new ports. But if I merely change the type of
> > one of mirrors in the test to from-lport, the test passes.
> >
> >
> > On the other hand, a consistent way to trigger the failure is adding a
> > 'sleep 3' at the end of a test case just before cleanup, apparently to
> > allow vswitchd to catch on the mirror updates and lock somewhere in
> > the code. I see vswitchd spinning at ~100% cpu in 'top' output when it
> > gets into this state. It's clearly doing SOMETHING, not just sleeping. :)
> >
> >
> > I suspect there's some bug inside vswitchd that makes it lock / spin
> > for a particular setup of mirrors. Whatever OVN sets up in vswitchd
> > database, the latter should not freeze. It would be helpful to provide
> > a short ovs-only reproducer for the situation that would not involve
> > OVN so that our OVS friends can take a look.
> >
> >
> > For the record, the mirrors in ovsdb are:
> >
> >
> > _uuid               : 491d0282-5e03-417c-b8cf-57f72a9a4c81
> > external_ids        : {}
> > name                : mirror0
> > output_port         : 4d0865e8-85a6-42a9-a005-faaecd88fb1c
> > output_vlan         : []
> > select_all          : false
> > select_dst_port     : [0a610fb2-86c2-4e0a-9611-788283b839ab,
> > 7c3e23f4-d42b-457c-89e4-6ccb6e16baaf]
> > select_src_port     : []
> > select_vlan         : []
> > snaplen             : []
> > statistics          : {}
> >
> > _uuid               : ad391ceb-450a-45d2-9b43-665e2515a148
> > external_ids        : {}
> > name                : mirror1
> > output_port         : b1cecc0c-8290-448e-b21a-0b3df8724697
> > output_vlan         : []
> > select_all          : false
> > select_dst_port     : [17958da9-8bea-450e-982b-1842f6a8f6e6,
> > 4cbc1c03-9e14-419e-81c0-9b1dc49fc528]
> > select_src_port     : []
> > select_vlan         : []
> > snaplen             : []
> > statistics          : {}
> >
> >
> > Bridge output here:
> >
> >
> > 8d4b8f3c-41c0-4cf0-a10f-1f0420a93971
> >     Bridge br-int
> >         fail_mode: secure
> >         datapath_type: system
> >         Port vif4
> >             Interface vif4
> >         Port vif2
> >             Interface vif2
> >         Port br-int
> >             Interface br-int
> >                 type: internal
> >         Port vif1
> >             Interface vif1
> >         Port ovn-mirror0
> >             Interface ovn-mirror0
> >                 type: gre
> >                 options: {key="0", remote_ip="192.168.1.12"}
> >         Port vif3
> >             Interface vif3
> >         Port ovn-mirror1
> >             Interface ovn-mirror1
> >                 type: gre
> >                 options: {key="1", remote_ip="192.168.1.12"}
> >         Port patch-br-int-to-ln-public
> >             Interface patch-br-int-to-ln-public
> >                 type: patch
> >                 options: {peer=patch-ln-public-to-br-int}
> >     Bridge br-phys
> >         Port br-phys
> >             Interface br-phys
> >                 type: internal
> >                 options:
> > {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-rx.pcap",
> > tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys-tx.pcap"}
> >         Port br-phys_n1
> >             Interface br-phys_n1
> >                 options:
> > {rxq_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-rx.pcap",
> > stream="unix:/home/vagrant/ovn/tests/testsuite.dir/0401/main/hv1_br-phys.sock",
> > tx_pcap="/home/vagrant/ovn/tests/testsuite.dir/0401/hv1/br-phys_n1-tx.pcap"}
> >
> >         Port patch-ln-public-to-br-int
> >             Interface patch-ln-public-to-br-int
> >                 type: patch
> >                 options: {peer=patch-br-int-to-ln-public}
> >
> Perhaps this may be of relevance: when I attach gdb to vswitchd process,
> I can see a never-ending stack of calls in thread 1 that looks like:
>
> #0  0x0000000000406760 in memcpy@plt ()
> #1  0x0000000000499d88 in dp_packet_clone_with_headroom
> (buffer=0x1c0d620, headroom=0) at lib/dp-packet.c:191
> #2  0x000000000049bd4d in dp_packet_batch_clone (dst=0x7fffcbf54740,
> src=0x7fffcbf55230) at lib/dp-packet.h:863
> #3  0x00000000004b15ef in dp_execute_output_action (pmd=0x7f638115f010,
> packets_=0x7fffcbf55230, should_steal=false, port_no=3) at
> lib/dpif-netdev.c:8696
> #4  0x00000000004b19a9 in dp_execute_cb (aux_=0x7fffcbf55200,
> packets_=0x7fffcbf55230, a=0x7fffcbf555c0, should_steal=false) at
> lib/dpif-netdev.c:8787
> #5  0x0000000000507834 in odp_execute_actions (dp=0x7fffcbf55200,
> batch=0x7fffcbf55230, steal=false, actions=0x7fffcbf55578,
> actions_len=88, dp_execute_action=0x4b18e6 <dp_execute_cb>)
>      at lib/odp-execute.c:993
> #6  0x00000000004b251e in dp_netdev_execute_actions (pmd=0x7f638115f010,
> packets=0x7fffcbf55230, should_steal=false, flow=0x7fffcbf55d60,
> actions=0x7fffcbf55578, actions_len=88)
>      at lib/dpif-netdev.c:9105
> #7  0x00000000004a6354 in dpif_netdev_execute (dpif=0x17af630,
> execute=0x7fffcbf55458) at lib/dpif-netdev.c:4557
> #8  0x00000000004a64d0 in dpif_netdev_operate (dpif=0x17af630,
> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
> lib/dpif-netdev.c:4606
> #9  0x00000000004bb9e8 in dpif_operate (dpif=0x17af630,
> ops=0x7fffcbf554a8, n_ops=1, offload_type=DPIF_OFFLOAD_AUTO) at
> lib/dpif.c:1372
> #10 0x00000000004bb8e1 in dpif_execute (dpif=0x17af630,
> execute=0x7fffcbf55500) at lib/dpif.c:1326
> #11 0x000000000043e46d in ofproto_dpif_execute_actions__
> (ofproto=0x17a8a10, version=7, flow=0x7fffcbf55d60, rule=0x0,
> ofpacts=0x7fffcbf56000, ofpacts_len=16, depth=58, resubmits=1924,
>      packet=0x7fffcbf56050) at ofproto/ofproto-dpif.c:4294
> #12 0x0000000000467e8a in compose_table_xlate (ctx=0x7fffcbf5f240,
> out_dev=0x1879430, packet=0x7fffcbf56050) at
> ofproto/ofproto-dpif-xlate.c:3526
> #13 0x0000000000468020 in tnl_send_arp_request (ctx=0x7fffcbf5f240,
> out_dev=0x1879430, eth_src=..., ip_src=184658112, ip_dst=201435328) at
> ofproto/ofproto-dpif-xlate.c:3555
> #14 0x0000000000468710 in native_tunnel_output (ctx=0x7fffcbf5f240,
> xport=0x1839bc0, flow=0x7fffcbf60930, tunnel_odp_port=7, truncate=false,
> is_last_action=false)
>      at ofproto/ofproto-dpif-xlate.c:3721
> #15 0x000000000046a78e in compose_output_action__ (ctx=0x7fffcbf5f240,
> ofp_port=7, xr=0x0, check_stp=true, is_last_action=false,
> truncate=false) at ofproto/ofproto-dpif-xlate.c:4356
> #16 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
> ofp_port=7, xr=0x0, is_last_action=false, truncate=false) at
> ofproto/ofproto-dpif-xlate.c:4416
> #17 0x00000000004653f4 in output_normal (ctx=0x7fffcbf5f240,
> out_xbundle=0x18918d0, xvlan=0x7fffcbf57160) at
> ofproto/ofproto-dpif-xlate.c:2533
> #18 0x0000000000464799 in mirror_packet (ctx=0x7fffcbf5f240,
> xbundle=0x1880d00, mirrors=2) at ofproto/ofproto-dpif-xlate.c:2190
> #19 0x000000000046a96b in compose_output_action__ (ctx=0x7fffcbf5f240,
> ofp_port=1, xr=0x0, check_stp=true, is_last_action=false,
> truncate=false) at ofproto/ofproto-dpif-xlate.c:4396
> #20 0x000000000046aa38 in compose_output_action (ctx=0x7fffcbf5f240,
> ofp_port=1, xr=0x0, is_last_action=false, truncate=false) at
> ofproto/ofproto-dpif-xlate.c:4416
> #21 0x000000000046cf3e in xlate_output_action (ctx=0x7fffcbf5f240,
> port=1, controller_len=0, may_packet_in=true, is_last_action=false,
> truncate=false, group_bucket_action=false)
>      at ofproto/ofproto-dpif-xlate.c:5361
> #22 0x0000000000471069 in do_xlate_actions (ofpacts=0x1860638,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7026
> #23 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
> rule=0x1860480, deepens=false, is_last_action=false,
> actions_xlator=0x470caf <do_xlate_actions>)
>      at ofproto/ofproto-dpif-xlate.c:4439
> #24 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
> in_port=5, table_id=65 'A', may_packet_in=false, honor_table_miss=false,
> with_ct_orig=false, is_last_action=false,
>      xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4568
> #25 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
> resubmit=0x185cfb8, is_last_action=false) at
> ofproto/ofproto-dpif-xlate.c:4879
> #26 0x0000000000471796 in do_xlate_actions (ofpacts=0x185cfb8,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #27 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcbf5f240,
> rule=0x185ce00, deepens=false, is_last_action=false,
> actions_xlator=0x470caf <do_xlate_actions>)
>      at ofproto/ofproto-dpif-xlate.c:4439
> #28 0x000000000046b0de in xlate_table_action (ctx=0x7fffcbf5f240,
> in_port=5, table_id=64 '@', may_packet_in=false, honor_table_miss=false,
> with_ct_orig=false, is_last_action=false,
>      xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4568
> #29 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcbf5f240,
> resubmit=0x1834458, is_last_action=false) at
> ofproto/ofproto-dpif-xlate.c:4879
> #30 0x0000000000471796 in do_xlate_actions (ofpacts=0x1834458,
> ofpacts_len=16, ctx=0x7fffcbf5f240, is_last_action=false,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
>
> it goes like that over and over and over for hundreds if not thousands
> of calls down the stack until
>
> #5738 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
> in_port=65533, table_id=9 '\t', may_packet_in=false,
> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
> #5739 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
> resubmit=0x1831ac0, is_last_action=true) at
> ofproto/ofproto-dpif-xlate.c:4879
> #5740 0x0000000000471796 in do_xlate_actions (ofpacts=0x1831a68,
> ofpacts_len=104, ctx=0x7fffcc09b330, is_last_action=true,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #5741 0x000000000046ab3b in xlate_recursively (ctx=0x7fffcc09b330,
> rule=0x18318b0, deepens=false, is_last_action=true,
> actions_xlator=0x470caf <do_xlate_actions>) at
> ofproto/ofproto-dpif-xlate.c:4439
> #5742 0x000000000046b0de in xlate_table_action (ctx=0x7fffcc09b330,
> in_port=65533, table_id=8 '\b', may_packet_in=false,
> honor_table_miss=false, with_ct_orig=false, is_last_action=true,
> xlator=0x470caf <do_xlate_actions>) at ofproto/ofproto-dpif-xlate.c:4568
> #5743 0x000000000046bbd3 in xlate_ofpact_resubmit (ctx=0x7fffcc09b330,
> resubmit=0x187bcc0, is_last_action=true) at
> ofproto/ofproto-dpif-xlate.c:4879
> #5744 0x0000000000471796 in do_xlate_actions (ofpacts=0x187bc80,
> ofpacts_len=80, ctx=0x7fffcc09b330, is_last_action=true,
> group_bucket_action=false) at ofproto/ofproto-dpif-xlate.c:7173
> #5745 0x0000000000473b7a in xlate_actions (xin=0x7fffcc09c5c0,
> xout=0x7fffcc09c910) at ofproto/ofproto-dpif-xlate.c:8033
> #5746 0x000000000043fac3 in packet_xlate (ofproto_=0x17c10e0,
> opo=0x7fffcc09ce00) at ofproto/ofproto-dpif.c:4877
> #5747 0x00000000004250d7 in ofproto_packet_out_start (ofproto=0x17c10e0,
> opo=0x7fffcc09ce00) at ofproto/ofproto.c:3698
> #5748 0x00000000004252bd in handle_packet_out (ofconn=0x17fde40,
> oh=0x17fdfa0) at ofproto/ofproto.c:3764
> #5749 0x000000000042fce2 in handle_single_part_openflow
> (ofconn=0x17fde40, oh=0x17fdfa0, type=OFPTYPE_PACKET_OUT) at
> ofproto/ofproto.c:8664
> #5750 0x0000000000430134 in handle_openflow (ofconn=0x17fde40,
> msgs=0x7fffcc09dc40) at ofproto/ofproto.c:8851
> #5751 0x000000000047a9ae in ofconn_run (ofconn=0x17fde40,
> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:1329
> #5752 0x0000000000478584 in connmgr_run (mgr=0x17e2ad0,
> handle_openflow=0x430072 <handle_openflow>) at ofproto/connmgr.c:356
> #5753 0x0000000000420f78 in ofproto_run (p=0x17c10e0) at
> ofproto/ofproto.c:1933
> #5754 0x00000000004101d4 in bridge_run__ () at vswitchd/bridge.c:3210
> #5755 0x00000000004103d3 in bridge_run () at vswitchd/bridge.c:3269
> #5756 0x0000000000415cf0 in main (argc=10, argv=0x7fffcc09dff8) at
> vswitchd/ovs-vswitchd.c:129
>
> The main thread never getting out of some processing code to reach any
> other handlers (e.g. for appctl requests?)
>
> I'm adding Ilya to CC in case he has an idea why vswitchd could lock /
> freeze / spin indefinitely on two to-lport mirror creation.
>
>
> > On 11/8/22 1:11 PM, Ihar Hrachyshka wrote:
> >> On Fri, Nov 4, 2022 at 3:09 PM Abhiram R N <abhiramrn@gmail.com> wrote:
> >>> Mirror creation just creates the mirror. The lsp-attach-mirror
> >>> triggers the sequence to create Mirror in OVS DB on compute node.
> >>> OVS already supports Port Mirroring.
> >>>
> >>> Note: This is targeted to mirror to destinations anywhere outside the
> >>> cluster where the analyser resides and it need not be an OVN node.
> >>>
> >>> Example commands are as below:
> >>>
> >>> Mirror creation
> >>> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
> >>>
> >>> Attach a logical port to the mirror.
> >>> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
> >>>
> >>> Detach a source from Mirror
> >>> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
> >>>
> >>> Mirror deletion
> >>> ovn-nbctl mirror-del mirror1
> >>>
> >>> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> >>> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> >>> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
> >>> ---
> >>> v12 --> V13: Made each of bulk test cases(in ovn.at) as separate
> >>>               test to make it pass consistently.
> >>> V11 --> V12: Minor fix in ovn.at to solve intermittent failures
> >>>
> >>> V10 --> V11: Addressed review comments from V10 by Ihar
> >>>             i) Expanded bulk updates test cases in ovn.at
> >>>                Overall below cases are covered
> >>>                a) Attaches multiple mirrors (new)
> >>>                b) Equal detaches and attaches (same as V10)
> >>>                c) Detaches more than attaches (new)
> >>>                d) Attaches more than detaches (new)
> >>>                e) Detaches all (new)
> >>>            ii) Addressed the detach all case in mirror.c
> >>>           iii) Minor correction in NEWS
> >>>            iv) Added invalid mirror attach case in ovn-nbctl.at
> >>>
> >>> Files modified (V10 --> V11):
> >>> Code --> mirror.c
> >>> Test --> ovn.at, ovn-nbctl.at
> >>> Misc --> NEWS
> >>>
> >>> Ihar,
> >>>      Regarding mirror_delete function param delete_all it is wrt the
> >>> port binding and if a port binding is removed we delete all its
> >>> attachment. Already that use case is covered in ovn.at.
> >>> Having said that the detaches all had issue in mirror_delete which
> >>> I have addressed. With all the above cases added now in bulk updates
> >>> hope it should give good assurance.
> >>>
> >>>   NEWS                        |   1 +
> >>>   controller/automake.mk      |   4 +-
> >>>   controller/mirror.c         | 538 +++++++++++++++++++++++++
> >>>   controller/mirror.h         |  53 +++
> >>>   controller/ovn-controller.c | 266 ++++++++++--
> >>>   northd/en-northd.c          |   4 +
> >>>   northd/inc-proc-northd.c    |   4 +
> >>>   northd/northd.c             | 172 ++++++++
> >>>   northd/northd.h             |   2 +
> >>>   ovn-nb.ovsschema            |  31 +-
> >>>   ovn-nb.xml                  |  63 +++
> >>>   ovn-sb.ovsschema            |  26 +-
> >>>   ovn-sb.xml                  |  50 +++
> >>>   tests/ovn-nbctl.at          | 120 ++++++
> >>>   tests/ovn-northd.at         | 102 +++++
> >>>   tests/ovn.at                | 778
> >>> ++++++++++++++++++++++++++++++++++++
> >>>   utilities/ovn-nbctl.c       | 357 +++++++++++++++++
> >>>   utilities/ovn-sbctl.c       |   4 +
> >>>   18 files changed, 2547 insertions(+), 28 deletions(-)
> >>>   create mode 100644 controller/mirror.c
> >>>   create mode 100644 controller/mirror.h
> >>>
> >>> diff --git a/NEWS b/NEWS
> >>> index 224a7b83e..84b22abdb 100644
> >>> --- a/NEWS
> >>> +++ b/NEWS
> >>> @@ -25,6 +25,7 @@ OVN v22.09.0 - 16 Sep 2022
> >>>       any of LR's LRP IP, there is no need to create SNAT entry.
> >>> Now such
> >>>       traffic destined to LRP IP is not dropped.
> >>>     - Bump python version required for building OVN to 3.6.
> >>> +  - Added Support for Remote Port Mirroring.
> >>>
> >>>   OVN v22.06.0 - 03 Jun 2022
> >>>   --------------------------
> >>> diff --git a/controller/automake.mk b/controller/automake.mk
> >>> index c2ab1bbe6..334672b4d 100644
> >>> --- a/controller/automake.mk
> >>> +++ b/controller/automake.mk
> >>> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
> >>>          controller/ovsport.h \
> >>>          controller/ovsport.c \
> >>>          controller/vif-plug.h \
> >>> -       controller/vif-plug.c
> >>> +       controller/vif-plug.c \
> >>> +       controller/mirror.h \
> >>> +       controller/mirror.c
> >>>
> >>>   controller_ovn_controller_LDADD = lib/libovn.la
> >>> $(OVS_LIBDIR)/libopenvswitch.la
> >>>   man_MANS += controller/ovn-controller.8
> >>> diff --git a/controller/mirror.c b/controller/mirror.c
> >>> new file mode 100644
> >>> index 000000000..11f2b63a6
> >>> --- /dev/null
> >>> +++ b/controller/mirror.c
> >>> @@ -0,0 +1,538 @@
> >>> +/* Copyright (c) 2022 Red Hat, Inc.
> >>> + *
> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
> >>> + * you may not use this file except in compliance with the License.
> >>> + * You may obtain a copy of the License at:
> >>> + *
> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
> >>> + *
> >>> + * Unless required by applicable law or agreed to in writing, software
> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> >>> implied.
> >>> + * See the License for the specific language governing permissions and
> >>> + * limitations under the License.
> >>> + */
> >>> +
> >>> +#include <config.h>
> >>> +#include <unistd.h>
> >>> +
> >>> +/* library headers */
> >>> +#include "lib/sset.h"
> >>> +#include "lib/util.h"
> >>> +
> >>> +/* OVS includes. */
> >>> +#include "lib/vswitch-idl.h"
> >>> +#include "openvswitch/vlog.h"
> >>> +
> >>> +/* OVN includes. */
> >>> +#include "binding.h"
> >>> +#include "lib/ovn-sb-idl.h"
> >>> +#include "mirror.h"
> >>> +
> >>> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> >>> +
> >>> +/* Static function declarations */
> >>> +
> >>> +static const struct ovsrec_port *
> >>> +get_port_for_iface(const struct ovsrec_interface *iface,
> >>> +                  const struct ovsrec_bridge *br_int)
> >>> +{
> >>> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> >>> +        const struct ovsrec_port *p = br_int->ports[i];
> >>> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> >>> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> >>> +                return p;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static bool
> >>> +mirror_create(const struct sbrec_port_binding *pb,
> >>> +              struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct ovsrec_mirror *mirror = NULL;
> >>> +
> >>> +    if (pb->n_up && !pb->up[0]) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    if (pb->chassis != pm_ctx->chassis_rec) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    if (!pm_ctx->ovs_idl_txn) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +
> >>> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> >>> +    /* Loop through the mirror rules */
> >>> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> >>> +        /* check if the mirror already exists in OVS DB */
> >>> +        bool create_mirror = true;
> >>> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> >>> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
> >>> +                /* Mirror with same name already exists
> >>> +                 * No need to create mirror
> >>> +                 */
> >>> +                create_mirror = false;
> >>> +                break;
> >>> +            }
> >>> +        }
> >>> +
> >>> +        if (create_mirror) {
> >>> +
> >>> +            struct smap options = SMAP_INITIALIZER(&options);
> >>> +            char *port_name, *key;
> >>> +
> >>> +            key = xasprintf("%ld",(long int)
> >>> pb->mirror_rules[i]->index);
> >>> +            smap_add(&options, "remote_ip",
> >>> pb->mirror_rules[i]->sink);
> >>> +            smap_add(&options, "key", key);
> >>> +            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> >>> +                /* Set the ERSPAN index */
> >>> +                smap_add(&options, "erspan_idx", key);
> >>> +                smap_add(&options, "erspan_ver","1");
> >>> +
> >>> +            }
> >>> +            struct ovsrec_interface *iface =
> >>> + ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> >>> +            port_name = xasprintf("ovn-%s",
> >>> + pb->mirror_rules[i]->name);
> >>> +
> >>> +            ovsrec_interface_set_name(iface, port_name);
> >>> +            ovsrec_interface_set_type(iface,
> >>> pb->mirror_rules[i]->type);
> >>> +            ovsrec_interface_set_options(iface, &options);
> >>> +
> >>> +            struct ovsrec_port *port =
> >>> + ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> >>> +            ovsrec_port_set_name(port, port_name);
> >>> +            ovsrec_port_set_interfaces(port, &iface, 1);
> >>> +
> >>> + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> >>> +
> >>> +            smap_destroy(&options);
> >>> +            free(port_name);
> >>> +            free(key);
> >>> +
> >>> +            VLOG_INFO("Creating Mirror in OVS DB");
> >>> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> >>> + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> >>> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> >>> + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> >>> + mirror);
> >>> +        }
> >>> +
> >>> +        struct local_binding *lbinding = local_binding_find(
> >>> +                               pm_ctx->local_bindings,
> >>> pb->logical_port);
> >>> +        const struct ovsrec_port *p =
> >>> +                     get_port_for_iface(lbinding->iface,
> >>> pm_ctx->br_int);
> >>> +        if (p) {
> >>> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> >>> +            } else if
> >>> (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> >>> +            } else {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static void
> >>> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> >>> +                              struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    char *filter;
> >>> +    if ((ovs_mirror->n_select_dst_port)
> >>> +            && (ovs_mirror->n_select_src_port)) {
> >>> +        filter = "both";
> >>> +    } else if (ovs_mirror->n_select_dst_port) {
> >>> +        filter = "to-lport";
> >>> +    } else {
> >>> +        filter = "from-lport";
> >>> +    }
> >>> +
> >>> +    if (strcmp(sb_mirror->filter, filter)) {
> >>> +        if (!strcmp(sb_mirror->filter,"from-lport")
> >>> +                              && !strcmp(filter,"both")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> >>> +                              && !strcmp(filter,"both")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
> >>> +                              && !strcmp(filter,"from-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"both")
> >>> +                              && !strcmp(filter,"to-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> >>> +                              && !strcmp(filter,"from-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_src_port[i]);
> >>> +            }
> >>> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> >>> +                              && !strcmp(filter,"to-lport")) {
> >>> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port;
> >>> i++) {
> >>> + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> >>> + ovs_mirror->select_dst_port[i]);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> >>> +                                   struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    struct smap options = SMAP_INITIALIZER(&options);
> >>> +    char *key, *type;
> >>> +    struct ovsrec_interface *iface =
> >>> + ovs_mirror->output_port->interfaces[0];
> >>> +    struct smap *opts = &iface->options;
> >>> +
> >>> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> >>> +    if (erspan_ver) {
> >>> +        type = "erspan";
> >>> +    } else {
> >>> +        type = "gre";
> >>> +    }
> >>> +    if (strcmp(type, sb_mirror->type)) {
> >>> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> >>> +    }
> >>> +
> >>> +    key = xasprintf("%ld",(long int) sb_mirror->index);
> >>> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> >>> +    smap_add(&options, "key", key);
> >>> +
> >>> +    if (!strcmp(sb_mirror->type, "erspan")) {
> >>> +        /* Set the ERSPAN index */
> >>> +        smap_add(&options, "erspan_idx", key);
> >>> +        smap_add(&options, "erspan_ver","1");
> >>> +    }
> >>> +
> >>> +    ovsrec_interface_set_options(iface, &options);
> >>> +    smap_destroy(&options);
> >>> +    free(key);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +mirror_update(const struct sbrec_mirror *sb_mirror,
> >>> +              struct ovsrec_mirror *ovs_mirror)
> >>> +{
> >>> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> >>> +
> >>> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> >>> +}
> >>> +
> >>> +static bool
> >>> +mirror_delete(const struct sbrec_port_binding *pb,
> >>> +              struct port_mirror_ctx *pm_ctx,
> >>> +              struct shash *pb_mirror_map,
> >>> +              bool delete_all)
> >>> +{
> >>> +
> >>> +    if (!pm_ctx->ovs_idl_txn) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> >>> +
> >>> +    if (!delete_all) {
> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> >>> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> >>> +        }
> >>> +    }
> >>> +
> >>> +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
> >>> pb->n_mirror_rules) {
> >>> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> >>> +
> >>> +            struct ovsrec_mirror *ovs_mirror = NULL;
> >>> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> >>> + pb->mirror_rules[i]->name);
> >>> +            if (ovs_mirror) {
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    struct shash_node *mirror_node;
> >>> +    const struct sbrec_port_binding *sb_pb;
> >>> +    int attach_cnt = 0;
> >>> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> >>> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> >>> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> >>> +            /* Find if the mirror has other sources */
> >>> +            attach_cnt = 0;
> >>> +            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
> >>> + pm_ctx->port_binding_table) {
> >>> +                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
> >>> +                    if (!strcmp(sb_pb->mirror_rules[i]->name,
> >>> + ovs_mirror->name)) {
> >>> +                        attach_cnt++;
> >>> +                    }
> >>> +                }
> >>> +            }
> >>> +            if (attach_cnt) {
> >>> +                /* More than 1 source then just
> >>> +                 * update the mirror table
> >>> +                 */
> >>> +                bool done = false;
> >>> +                for (size_t i = 0; ((i <
> >>> ovs_mirror->n_select_dst_port)
> >>> +                                                   && (done ==
> >>> false)); i++) {
> >>> +                    const struct ovsrec_port *port_rec =
> >>> + ovs_mirror->select_dst_port[i];
> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
> >>> j++) {
> >>> +                        const struct ovsrec_interface *iface_rec;
> >>> +
> >>> +                        iface_rec = port_rec->interfaces[j];
> >>> +                        const char *iface_id =
> >>> + smap_get(&iface_rec->external_ids,
> >>> + "iface-id");
> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
> >>> + ovsrec_mirror_update_select_dst_port_delvalue(
> >>> + ovs_mirror, port_rec);
> >>> +                            done = true;
> >>> +                            break;
> >>> +                        }
> >>> +                    }
> >>> +                }
> >>> +                done = false;
> >>> +                for (size_t i = 0; ((i <
> >>> ovs_mirror->n_select_src_port)
> >>> +                                                   && (done ==
> >>> false)); i++) {
> >>> +                    const struct ovsrec_port *port_rec =
> >>> + ovs_mirror->select_src_port[i];
> >>> +                    for (size_t j = 0; j < port_rec->n_interfaces;
> >>> j++) {
> >>> +                        const struct ovsrec_interface *iface_rec;
> >>> +
> >>> +                        iface_rec = port_rec->interfaces[j];
> >>> +                        const char *iface_id =
> >>> + smap_get(&iface_rec->external_ids,
> >>> + "iface-id");
> >>> +                        if (!strcmp(iface_id,pb->logical_port)) {
> >>> + ovsrec_mirror_update_select_src_port_delvalue(
> >>> + ovs_mirror, port_rec);
> >>> +                            done = true;
> >>> +                            break;
> >>> +                        }
> >>> +                    }
> >>> +                }
> >>> +            } else {
> >>> +                /*
> >>> +                 * If only 1 source delete the output port
> >>> +                 * and then delete the mirror completely
> >>> +                 */
> >>> +                VLOG_INFO("Only 1 source for the mirror. Hence
> >>> delete it");
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    const char *used_node, *used_next;
> >>> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> >>> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> >>> +    }
> >>> +    sset_destroy(&pb_mirrors);
> >>> +
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static void
> >>> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> >>> +                            struct port_mirror_ctx *pm_ctx,
> >>> +                            struct shash *pb_mirror_map)
> >>> +{
> >>> +    const struct ovsrec_mirror *mirror = NULL;
> >>> +
> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> >>> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> >>> +            const struct ovsrec_port *port_rec =
> >>> mirror->select_dst_port[i];
> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> >>> +                const struct ovsrec_interface *iface_rec;
> >>> +                iface_rec = port_rec->interfaces[j];
> >>> +                const char *logical_port =
> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
> >>> mirror);
> >>> +                }
> >>> +            }
> >>> +        }
> >>> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> >>> +            const struct ovsrec_port *port_rec =
> >>> mirror->select_src_port[i];
> >>> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> >>> +                const struct ovsrec_interface *iface_rec;
> >>> +                iface_rec = port_rec->interfaces[j];
> >>> +                const char *logical_port =
> >>> +                    smap_get(&iface_rec->external_ids, "iface-id");
> >>> +                if (!strcmp(logical_port, pb->logical_port)) {
> >>> +                    shash_add_once(pb_mirror_map, mirror->name,
> >>> mirror);
> >>> +                }
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +void
> >>> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>> +{
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> >>> +
> >>> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> >>> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> >>> +}
> >>> +
> >>> +
> >>> +void
> >>> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> >>> +{
> >>> +    shash_init(ovs_mirrors);
> >>> +}
> >>> +
> >>> +void
> >>> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct sbrec_port_binding *pb;
> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> >>> + pm_ctx->port_binding_table) {
> >>> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> >>> +    }
> >>> +}
> >>> +
> >>> +bool
> >>> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> >>> bool removed,
> >>> +                     struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    bool ret = true;
> >>> +    struct local_binding *lbinding = local_binding_find(
> >>> +                               pm_ctx->local_bindings,
> >>> pb->logical_port);
> >>> +
> >>> +    if (strcmp(pb->type, "") && (!lbinding)) {
> >>> +        return ret;
> >>> +    }
> >>> +
> >>> +    struct shash port_ovs_mirrors =
> >>> SHASH_INITIALIZER(&port_ovs_mirrors);
> >>> +
> >>> +    /* Need to find if mirror needs update */
> >>> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> >>> +    if (!removed) {
> >>> +        if ((pb->n_mirror_rules == 0)
> >>> +              && (shash_is_empty(&port_ovs_mirrors))) {
> >>> +            /* No mirror update */
> >>> +        } else if (pb->n_mirror_rules ==
> >>> shash_count(&port_ovs_mirrors)) {
> >>> +            /* Though number of mirror rules are same,
> >>> +             * need to verify the contents
> >>> +             */
> >>> +            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> >>> +                if (!shash_find(&port_ovs_mirrors,
> >>> + pb->mirror_rules[i]->name)) {
> >>> +                    /* Mis match in OVN SB DB and OVS DB
> >>> +                     * Delete and Create mirror(s) with proper sources
> >>> +                     */
> >>> +                    ret = mirror_delete(pb, pm_ctx,
> >>> + &port_ovs_mirrors, false);
> >>> +                    if (ret) {
> >>> +                        ret = mirror_create(pb, pm_ctx);
> >>> +                    }
> >>> +                    break;
> >>> +                }
> >>> +            }
> >>> +        } else {
> >>> +            /* Update Mirror */
> >>> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> >>> +                /* create mirror,
> >>> +                 * if mirror already exists only update selection
> >>> +                 */
> >>> +                ret = mirror_create(pb, pm_ctx);
> >>> +            } else {
> >>> +                /* delete mirror,
> >>> +                 * if mirror has other sources only update selection
> >>> +                 */
> >>> +                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors,
> >>> false);
> >>> +            }
> >>> +        }
> >>> +    } else {
> >>> +        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
> >>> +    }
> >>> +
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> + &port_ovs_mirrors) {
> >>> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +    shash_destroy(&port_ovs_mirrors);
> >>> +
> >>> +    return ret;
> >>> +}
> >>> +
> >>> +bool
> >>> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    const struct sbrec_mirror *mirror = NULL;
> >>> +    struct ovsrec_mirror *ovs_mirror = NULL;
> >>> +
> >>> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
> >>> pm_ctx->sb_mirror_table) {
> >>> +    /* For each tracked mirror entry check if OVS entry is there*/
> >>> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> >>> mirror->name);
> >>> +        if (ovs_mirror) {
> >>> +            if (sbrec_mirror_is_deleted(mirror)) {
> >>> +                /* Need to delete the mirror in OVS */
> >>> +                VLOG_INFO("Delete mirror and remove port");
> >>> + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror->output_port);
> >>> + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> >>> + ovs_mirror);
> >>> + ovsrec_port_delete(ovs_mirror->output_port);
> >>> +                ovsrec_mirror_delete(ovs_mirror);
> >>> +            } else {
> >>> +                mirror_update(mirror, ovs_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    return true;
> >>> +}
> >>> +
> >>> +void
> >>> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> >>> +{
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> +                                              ovs_mirrors) {
> >>> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +    shash_destroy(ovs_mirrors);
> >>> +}
> >>> diff --git a/controller/mirror.h b/controller/mirror.h
> >>> new file mode 100644
> >>> index 000000000..85b964f55
> >>> --- /dev/null
> >>> +++ b/controller/mirror.h
> >>> @@ -0,0 +1,53 @@
> >>> +/* Copyright (c) 2022 Red Hat, Inc.
> >>> + *
> >>> + * Licensed under the Apache License, Version 2.0 (the "License");
> >>> + * you may not use this file except in compliance with the License.
> >>> + * You may obtain a copy of the License at:
> >>> + *
> >>> + *     http://www.apache.org/licenses/LICENSE-2.0
> >>> + *
> >>> + * Unless required by applicable law or agreed to in writing, software
> >>> + * distributed under the License is distributed on an "AS IS" BASIS,
> >>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
> >>> implied.
> >>> + * See the License for the specific language governing permissions and
> >>> + * limitations under the License.
> >>> + */
> >>> +
> >>> +#ifndef OVN_MIRROR_H
> >>> +#define OVN_MIRROR_H 1
> >>> +
> >>> +struct ovsdb_idl_txn;
> >>> +struct ovsrec_port_table;
> >>> +struct ovsrec_bridge;
> >>> +struct ovsrec_bridge_table;
> >>> +struct ovsrec_open_vswitch_table;
> >>> +struct sbrec_chassis;
> >>> +struct ovsrec_interface_table;
> >>> +struct ovsrec_mirror_table;
> >>> +struct sbrec_mirror_table;
> >>> +struct sbrec_port_binding_table;
> >>> +
> >>> +struct port_mirror_ctx {
> >>> +    struct shash *ovs_mirrors;
> >>> +    struct ovsdb_idl_txn *ovs_idl_txn;
> >>> +    const struct ovsrec_port_table *port_table;
> >>> +    const struct ovsrec_bridge *br_int;
> >>> +    const struct sbrec_chassis *chassis_rec;
> >>> +    const struct ovsrec_bridge_table *bridge_table;
> >>> +    const struct ovsrec_open_vswitch_table *ovs_table;
> >>> +    const struct ovsrec_interface_table *iface_table;
> >>> +    const struct ovsrec_mirror_table *mirror_table;
> >>> +    const struct sbrec_mirror_table *sb_mirror_table;
> >>> +    const struct sbrec_port_binding_table *port_binding_table;
> >>> +    struct shash *local_bindings;
> >>> +};
> >>> +
> >>> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> >>> +void ovn_port_mirror_init(struct shash *);
> >>> +void ovn_port_mirror_destroy(struct shash *);
> >>> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> >>> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> >>> +                                  bool removed,
> >>> +                                  struct port_mirror_ctx *pm_ctx);
> >>> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> >>> +#endif
> >>> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> >>> index 8895c7a2b..15ab17c4a 100644
> >>> --- a/controller/ovn-controller.c
> >>> +++ b/controller/ovn-controller.c
> >>> @@ -78,6 +78,7 @@
> >>>   #include "lib/inc-proc-eng.h"
> >>>   #include "lib/ovn-l7.h"
> >>>   #include "hmapx.h"
> >>> +#include "mirror.h"
> >>>
> >>>   VLOG_DEFINE_THIS_MODULE(main);
> >>>
> >>> @@ -966,6 +967,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> >>>       ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
> >>>       ovsdb_idl_track_add_column(ovs_idl,
> >>> &ovsrec_port_col_external_ids);
> >>> +    mirror_register_ovs_idl(ovs_idl);
> >>>   }
> >>>
> >>>   #define SB_NODES \
> >>> @@ -986,6 +988,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >>>       SB_NODE(load_balancer, "load_balancer") \
> >>>       SB_NODE(fdb, "fdb") \
> >>>       SB_NODE(meter, "meter") \
> >>> +    SB_NODE(mirror, "mirror") \
> >>>       SB_NODE(static_mac_binding, "static_mac_binding")
> >>>
> >>>   enum sb_engine_node {
> >>> @@ -1003,7 +1006,8 @@ enum sb_engine_node {
> >>>       OVS_NODE(bridge, "bridge") \
> >>>       OVS_NODE(port, "port") \
> >>>       OVS_NODE(interface, "interface") \
> >>> -    OVS_NODE(qos, "qos")
> >>> +    OVS_NODE(qos, "qos") \
> >>> +    OVS_NODE(mirror, "mirror")
> >>>
> >>>   enum ovs_engine_node {
> >>>   #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> >>> @@ -2383,6 +2387,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
> >>>       free(lbs);
> >>>   }
> >>>
> >>> +/* Mirror Engine */
> >>> +struct ed_type_port_mirror {
> >>> +    struct shash ovs_mirrors;
> >>> +};
> >>> +
> >>> +static void *
> >>> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> >>> +                    struct engine_arg *arg OVS_UNUSED)
> >>> +{
> >>> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof
> >>> *port_mirror);
> >>> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> >>> +    return port_mirror;
> >>> +}
> >>> +
> >>> +static void
> >>> +en_port_mirror_cleanup(void *data)
> >>> +{
> >>> +    struct ed_type_port_mirror *port_mirror = data;
> >>> + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> >>> +}
> >>> +
> >>> +static void
> >>> +init_port_mirror_ctx(struct engine_node *node,
> >>> +                 struct ed_type_runtime_data *rt_data,
> >>> +                 struct ed_type_port_mirror *port_mirror_data,
> >>> +                 struct port_mirror_ctx *pm_ctx)
> >>> +{
> >>> +    struct ovsrec_open_vswitch_table *ovs_table =
> >>> +        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_open_vswitch", node));
> >>> +    struct ovsrec_bridge_table *bridge_table =
> >>> +        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_bridge", node));
> >>> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> >>> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> >>> ovs_table);
> >>> +
> >>> +    ovs_assert(br_int && chassis_id);
> >>> +    const struct sbrec_chassis *chassis = NULL;
> >>> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> >>> +        engine_ovsdb_node_get_index(
> >>> +                engine_get_input("SB_chassis", node),
> >>> +                "name");
> >>> +
> >>> +    if (chassis_id) {
> >>> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name,
> >>> chassis_id);
> >>> +    }
> >>> +    ovs_assert(chassis);
> >>> +
> >>> +    struct ovsrec_port_table *port_table =
> >>> +        (struct ovsrec_port_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_port", node));
> >>> +
> >>> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> >>> +        engine_get_input_data("ovs_interface_shadow", node);
> >>> +
> >>> +    struct ovsrec_mirror_table *mirror_table =
> >>> +        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("OVS_mirror", node));
> >>> +
> >>> +    struct sbrec_port_binding_table *pb_table =
> >>> +        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("SB_port_binding", node));
> >>> +
> >>> +    struct sbrec_mirror_table *sb_mirror_table =
> >>> +        (struct sbrec_mirror_table *) EN_OVSDB_GET(
> >>> +            engine_get_input("SB_mirror", node));
> >>> +
> >>> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> >>> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> >>> + &port_mirror_data->ovs_mirrors) {
> >>> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> >>> +    }
> >>> +
> >>> +    const struct ovsrec_mirror *ovsmirror = NULL;
> >>> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> >>> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
> >>> ovsmirror);
> >>> +    }
> >>> +
> >>> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> >>> +    pm_ctx->port_table = port_table;
> >>> +    pm_ctx->iface_table = iface_shadow->iface_table;
> >>> +    pm_ctx->mirror_table = mirror_table;
> >>> +    pm_ctx->port_binding_table = pb_table;
> >>> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> >>> +    pm_ctx->br_int = br_int;
> >>> +    pm_ctx->chassis_rec = chassis;
> >>> +    pm_ctx->bridge_table = bridge_table;
> >>> +    pm_ctx->ovs_table = ovs_table;
> >>> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> >>> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> >>> +}
> >>> +
> >>> +static void
> >>> +en_port_mirror_run(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    ovn_port_mirror_run(&pm_ctx);
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    /* There is no tracked data. Fall back to full recompute of
> >>> port_mirror */
> >>> +    if (!rt_data->tracked) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> >>> +    if (hmap_is_empty(tracked_dp_bindings)) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    struct tracked_datapath *tdp;
> >>> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> >>> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> >>> +            /* Fall back to full recompute when a local datapath
> >>> +             * is added or deleted. */
> >>> +            return false;
> >>> +        }
> >>> +
> >>> +        struct shash_node *shash_node;
> >>> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> >>> +            struct tracked_lport *lport = shash_node->data;
> >>> +            bool removed =
> >>> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ?
> >>> true: false;
> >>> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed,
> >>> &pm_ctx)) {
> >>> +                return false;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    /* handle port binding updates (i.,e when the mirror column
> >>> +     * of port_binding is updated)
> >>> +     */
> >>> +    const struct sbrec_port_binding *pb;
> >>> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> >>> + pm_ctx.port_binding_table) {
> >>> +        bool removed = sbrec_port_binding_is_deleted(pb);
> >>> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> >>> +            return false;
> >>> +        }
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +
> >>> +}
> >>> +
> >>> +static bool
> >>> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> >>> +{
> >>> +    struct port_mirror_ctx pm_ctx;
> >>> +    struct ed_type_port_mirror *port_mirror_data = data;
> >>> +    struct ed_type_runtime_data *rt_data =
> >>> +        engine_get_input_data("runtime_data", node);
> >>> +
> >>> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> >>> +
> >>> +    /* handle sb mirror updates
> >>> +     */
> >>> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> >>> +        return false;
> >>> +    }
> >>> +
> >>> +    engine_set_node_state(node, EN_UPDATED);
> >>> +    return true;
> >>> +
> >>> +}
> >>> +
> >>>   /* Engine node which is used to handle the Non VIF data like
> >>>    *   - OVS patch ports
> >>>    *   - Tunnel ports and the related chassis information.
> >>> @@ -3704,6 +3905,7 @@ main(int argc, char *argv[])
> >>>       ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
> >>>       ENGINE_NODE(northd_options, "northd_options");
> >>>       ENGINE_NODE(dhcp_options, "dhcp_options");
> >>> +    ENGINE_NODE(port_mirror, "port_mirror");
> >>>
> >>>   #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> >>>       SB_NODES
> >>> @@ -3862,6 +4064,22 @@ main(int argc, char *argv[])
> >>>       engine_add_input(&en_flow_output, &en_pflow_output,
> >>>                        flow_output_pflow_output_handler);
> >>>
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_port,
> >>> engine_noop_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> >>> +                     engine_noop_handler);
> >>> +    engine_add_input(&en_flow_output, &en_port_mirror,
> >>> +                     engine_noop_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> >>> +                     port_mirror_runtime_data_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> >>> +                     port_mirror_sb_mirror_handler);
> >>> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> >>> +                     port_mirror_port_binding_handler);
> >>> +
> >>>       struct engine_arg engine_arg = {
> >>>           .sb_idl = ovnsb_idl_loop.idl,
> >>>           .ovs_idl = ovs_idl_loop.idl,
> >>> @@ -4131,34 +4349,36 @@ main(int argc, char *argv[])
> >>>
> >>> stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
> >>>                                       time_msec());
> >>> -                    if (ovnsb_idl_txn) {
> >>> -                        if (ofctrl_has_backlog()) {
> >>> -                            /* When there are in-flight messages
> >>> pending to
> >>> -                             * ovs-vswitchd, we should hold on
> >>> recomputing so
> >>> -                             * that the previous flow installations
> >>> won't be
> >>> -                             * delayed.  However, we still want to
> >>> try if
> >>> -                             * recompute is not needed and we can
> >>> quickly
> >>> -                             * incrementally process the new
> >>> changes, to avoid
> >>> -                             * unnecessarily forced recomputes
> >>> later on.  This
> >>> -                             * is because the OVSDB change tracker
> >>> cannot
> >>> -                             * preserve tracked changes across
> >>> iterations.  If
> >>> -                             * change tracking is improved, we can
> >>> simply skip
> >>> -                             * this round of engine_run and
> >>> continue processing
> >>> -                             * acculated changes incrementally
> >>> later when
> >>> -                             * ofctrl_has_backlog() returns false. */
> >>> -                            engine_run(false);
> >>> -                        } else {
> >>> -                            engine_run(true);
> >>> -                        }
> >>> -                    } else {
> >>> -                        /* Even if there's no SB DB transaction
> >>> available,
> >>> +
> >>> +                    bool allow_engine_recompute = true;
> >>> +
> >>> +                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
> >>> + ofctrl_has_backlog()) {
> >>> +                        /* When there are in-flight messages
> >>> pending to
> >>> +                         * ovs-vswitchd, we should hold on
> >>> recomputing so
> >>> +                         * that the previous flow installations
> >>> won't be
> >>> +                         * delayed.  However, we still want to try if
> >>> +                         * recompute is not needed and we can quickly
> >>> +                         * incrementally process the new changes,
> >>> to avoid
> >>> +                         * unnecessarily forced recomputes later
> >>> on.  This
> >>> +                         * is because the OVSDB change tracker cannot
> >>> +                         * preserve tracked changes across
> >>> iterations.  If
> >>> +                         * change tracking is improved, we can
> >>> simply skip
> >>> +                         * this round of engine_run and continue
> >>> processing
> >>> +                         * acculated changes incrementally later when
> >>> +                         * ofctrl_has_backlog() returns false. */
> >>> +
> >>> +                        /* Even if there's no SB/OVS DB transaction
> >>> available,
> >>>                            * try to run the engine so that we can
> >>> handle any
> >>>                            * incremental changes that don't require
> >>> a recompute.
> >>>                            * If a recompute is required, the engine
> >>> will abort,
> >>>                            * triggerring a full run in the next
> >>> iteration.
> >>>                            */
> >>> -                        engine_run(false);
> >>> +                        allow_engine_recompute = false;
> >>>                       }
> >>> +
> >>> +                    engine_run(allow_engine_recompute);
> >>> +
> >>> stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
> >>>                                      time_msec());
> >>>                       if (engine_has_updated()) {
> >>> diff --git a/northd/en-northd.c b/northd/en-northd.c
> >>> index 7fe83db64..608220b1f 100644
> >>> --- a/northd/en-northd.c
> >>> +++ b/northd/en-northd.c
> >>> @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void
> >>> *data)
> >>>           EN_OVSDB_GET(engine_get_input("NB_acl", node));
> >>>       input_data.nbrec_static_mac_binding_table =
> >>> EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> >>> +    input_data.nbrec_mirror_table =
> >>> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
> >>>
> >>>       input_data.sbrec_sb_global_table =
> >>>           EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> >>> @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node,
> >>> void *data)
> >>>           EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
> >>>       input_data.sbrec_static_mac_binding_table =
> >>> EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> >>> +    input_data.sbrec_mirror_table =
> >>> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
> >>>
> >>>       northd_run(&input_data, data,
> >>>                  eng_ctx->ovnnb_idl_txn,
> >>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> >>> index 54e0ad3b0..ac27a730e 100644
> >>> --- a/northd/inc-proc-northd.c
> >>> +++ b/northd/inc-proc-northd.c
> >>> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >>>       NB_NODE(acl, "acl") \
> >>>       NB_NODE(logical_router, "logical_router") \
> >>>       NB_NODE(qos, "qos") \
> >>> +    NB_NODE(mirror, "mirror") \
> >>>       NB_NODE(meter, "meter") \
> >>>       NB_NODE(meter_band, "meter_band") \
> >>>       NB_NODE(logical_router_port, "logical_router_port") \
> >>> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >>>       SB_NODE(logical_flow, "logical_flow") \
> >>>       SB_NODE(logical_dp_group, "logical_DP_group") \
> >>>       SB_NODE(multicast_group, "multicast_group") \
> >>> +    SB_NODE(mirror, "mirror") \
> >>>       SB_NODE(meter, "meter") \
> >>>       SB_NODE(meter_band, "meter_band") \
> >>>       SB_NODE(datapath_binding, "datapath_binding") \
> >>> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
> >>> *nb,
> >>>       engine_add_input(&en_northd, &en_nb_acl, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_logical_router, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_qos, NULL);
> >>> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_meter, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_meter_band, NULL);
> >>>       engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> >>> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop
> >>> *nb,
> >>>       engine_add_input(&en_northd, &en_sb_address_set, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_port_group, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> >>> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_meter, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_meter_band, NULL);
> >>>       engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> >>> diff --git a/northd/northd.c b/northd/northd.c
> >>> index b7388afc5..52abdda28 100644
> >>> --- a/northd/northd.c
> >>> +++ b/northd/northd.c
> >>> @@ -3248,6 +3248,89 @@ ovn_port_update_sbrec_chassis(
> >>>       free(requested_chassis_sb);
> >>>   }
> >>>
> >>> +static void
> >>> +do_sb_mirror_addition(struct northd_input *input_data,
> >>> +                      const struct ovn_port *op)
> >>> +{
> >>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +        const struct sbrec_mirror *sb_mirror;
> >>> +        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> >>> + input_data->sbrec_mirror_table) {
> >>> +            if (!strcmp(sb_mirror->name,
> >>> + op->nbsp->mirror_rules[i]->name)) {
> >>> +                /* Add the value to SB */
> >>> + sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> >>> + sb_mirror);
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +sbrec_port_binding_update_mirror_rules(struct northd_input
> >>> *input_data,
> >>> +                                       const struct ovn_port *op)
> >>> +{
> >>> +    size_t i = 0;
> >>> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> >>> +        /* Needs deletion in SB */
> >>> +        struct shash nb_mirror_rules =
> >>> SHASH_INITIALIZER(&nb_mirror_rules);
> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +            shash_add(&nb_mirror_rules,
> >>> + op->nbsp->mirror_rules[i]->name,
> >>> + op->nbsp->mirror_rules[i]);
> >>> +        }
> >>> +
> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> >>> +            if (!shash_find(&nb_mirror_rules,
> >>> + op->sb->mirror_rules[i]->name)) {
> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> >>> + op->sb->mirror_rules[i]);
> >>> +            }
> >>> +        }
> >>> +
> >>> +        struct shash_node *node, *next;
> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> >>> +            shash_delete(&nb_mirror_rules, node);
> >>> +        }
> >>> +        shash_destroy(&nb_mirror_rules);
> >>> +
> >>> +    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
> >>> +        /* Needs addition in SB */
> >>> +        do_sb_mirror_addition(input_data, op);
> >>> +    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
> >>> +        /*
> >>> +         * Check if its the same mirrors on both SB and NB DBs
> >>> +         * If not update accordingly.
> >>> +         */
> >>> +        bool needs_sb_addition = false;
> >>> +        struct shash nb_mirror_rules =
> >>> SHASH_INITIALIZER(&nb_mirror_rules);
> >>> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> >>> +            shash_add(&nb_mirror_rules,
> >>> + op->nbsp->mirror_rules[i]->name,
> >>> + op->nbsp->mirror_rules[i]);
> >>> +        }
> >>> +
> >>> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> >>> +            if (!shash_find(&nb_mirror_rules,
> >>> + op->sb->mirror_rules[i]->name)) {
> >>> + sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> >>> + op->sb->mirror_rules[i]);
> >>> +                    needs_sb_addition = true;
> >>> +            }
> >>> +        }
> >>> +
> >>> +        struct shash_node *node, *next;
> >>> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> >>> +            shash_delete(&nb_mirror_rules, node);
> >>> +        }
> >>> +        shash_destroy(&nb_mirror_rules);
> >>> +
> >>> +        if (needs_sb_addition) {
> >>> +            do_sb_mirror_addition(input_data, op);
> >>> +        }
> >>> +    }
> >>> +}
> >>> +
> >>>   static void
> >>>   ovn_port_update_sbrec(struct northd_input *input_data,
> >>>                         struct ovsdb_idl_txn *ovnsb_txn,
> >>> @@ -3607,6 +3690,15 @@ ovn_port_update_sbrec(struct northd_input
> >>> *input_data,
> >>>           }
> >>>           sbrec_port_binding_set_external_ids(op->sb, &ids);
> >>>           smap_destroy(&ids);
> >>> +
> >>> +        if (!op->nbsp->n_mirror_rules) {
> >>> +            /* Nothing is set. Clear mirror_rules from pb. */
> >>> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> >>> +        } else {
> >>> +            /* Check if SB DB update needed */
> >>> + sbrec_port_binding_update_mirror_rules(input_data, op);
> >>> +        }
> >>> +
> >>>       }
> >>>       if (op->tunnel_key != op->sb->tunnel_key) {
> >>>           sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> >>> @@ -15014,6 +15106,85 @@ sync_meters(struct northd_input *input_data,
> >>>       shash_destroy(&sb_meters);
> >>>   }
> >>>
> >>> +static bool
> >>> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> >>> +                  const struct sbrec_mirror *sb_mirror)
> >>> +{
> >>> +
> >>> +    if (nb_mirror->index != sb_mirror->index) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> >>> +        return true;
> >>> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> >>> +        return true;
> >>> +    }
> >>> +
> >>> +    return false;
> >>> +}
> >>> +
> >>> +static void
> >>> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> >>> +                             const char *mirror_name,
> >>> +                             const struct nbrec_mirror *nb_mirror,
> >>> +                             struct shash *sb_mirrors,
> >>> +                             struct sset *used_sb_mirrors)
> >>> +{
> >>> +    const struct sbrec_mirror *sb_mirror;
> >>> +    bool new_sb_mirror = false;
> >>> +
> >>> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> >>> +    if (!sb_mirror) {
> >>> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> >>> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> >>> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> >>> +        new_sb_mirror = true;
> >>> +    }
> >>> +    sset_add(used_sb_mirrors, mirror_name);
> >>> +
> >>> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror,
> >>> sb_mirror)) {
> >>> + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> >>> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> >>> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> >>> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +sync_mirrors(struct northd_input *input_data,
> >>> +            struct ovsdb_idl_txn *ovnsb_txn)
> >>> +{
> >>> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> >>> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> >>> +
> >>> +    const struct sbrec_mirror *sb_mirror;
> >>> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> >>> input_data->sbrec_mirror_table) {
> >>> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> >>> +    }
> >>> +
> >>> +    const struct nbrec_mirror *nb_mirror;
> >>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
> >>> input_data->nbrec_mirror_table) {
> >>> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
> >>> nb_mirror,
> >>> +                                     &sb_mirrors, &used_sb_mirrors);
> >>> +    }
> >>> +
> >>> +    const char *used_mirror;
> >>> +    const char *used_mirror_next;
> >>> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
> >>> &used_sb_mirrors) {
> >>> +        shash_find_and_delete(&sb_mirrors, used_mirror);
> >>> +        sset_delete(&used_sb_mirrors,
> >>> SSET_NODE_FROM_NAME(used_mirror));
> >>> +    }
> >>> +    sset_destroy(&used_sb_mirrors);
> >>> +
> >>> +    struct shash_node *node, *next;
> >>> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> >>> +        sbrec_mirror_delete(node->data);
> >>> +        shash_delete(&sb_mirrors, node);
> >>> +    }
> >>> +    shash_destroy(&sb_mirrors);
> >>> +}
> >>> +
> >>>   /*
> >>>    * struct 'dns_info' is used to sync the DNS records between OVN
> >>> Northbound db
> >>>    * and Southbound db.
> >>> @@ -15644,6 +15815,7 @@ ovnnb_db_run(struct northd_input *input_data,
> >>>       sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
> >>>       sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
> >>>       sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> >>> +    sync_mirrors(input_data, ovnsb_txn);
> >>>       sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
> >>>       cleanup_stale_fdb_entries(input_data, &data->datapaths);
> >>>       stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> >>> diff --git a/northd/northd.h b/northd/northd.h
> >>> index da90e2815..17a62ea10 100644
> >>> --- a/northd/northd.h
> >>> +++ b/northd/northd.h
> >>> @@ -36,6 +36,7 @@ struct northd_input {
> >>>       const struct nbrec_acl_table *nbrec_acl_table;
> >>>       const struct nbrec_static_mac_binding_table
> >>>           *nbrec_static_mac_binding_table;
> >>> +    const struct nbrec_mirror_table *nbrec_mirror_table;
> >>>
> >>>       /* Southbound table references */
> >>>       const struct sbrec_sb_global_table *sbrec_sb_global_table;
> >>> @@ -55,6 +56,7 @@ struct northd_input {
> >>>       const struct sbrec_chassis_private_table
> >>> *sbrec_chassis_private_table;
> >>>       const struct sbrec_static_mac_binding_table
> >>>           *sbrec_static_mac_binding_table;
> >>> +    const struct sbrec_mirror_table *sbrec_mirror_table;
> >>>
> >>>       /* Indexes */
> >>>       struct ovsdb_idl_index *sbrec_chassis_by_name;
> >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> >>> index 174364c8b..01de55222 100644
> >>> --- a/ovn-nb.ovsschema
> >>> +++ b/ovn-nb.ovsschema
> >>> @@ -1,7 +1,7 @@
> >>>   {
> >>>       "name": "OVN_Northbound",
> >>> -    "version": "6.3.0",
> >>> -    "cksum": "4042813038 31869",
> >>> +    "version": "6.4.0",
> >>> +    "cksum": "589874483 33352",
> >>>       "tables": {
> >>>           "NB_Global": {
> >>>               "columns": {
> >>> @@ -132,6 +132,11 @@
> >>>                                               "refType": "weak"},
> >>>                                    "min": 0,
> >>>                                    "max": 1}},
> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> >>> +                                          "refTable": "Mirror",
> >>> +                                          "refType": "weak"},
> >>> +                                  "min": 0,
> >>> +                                  "max": "unlimited"}},
> >>>                   "ha_chassis_group": {
> >>>                       "type": {"key": {"type": "uuid",
> >>>                                        "refTable": "HA_Chassis_Group",
> >>> @@ -301,6 +306,28 @@
> >>>                       "type": {"key": "string", "value": "string",
> >>>                                "min": 0, "max": "unlimited"}}},
> >>>               "isRoot": false},
> >>> +        "Mirror": {
> >>> +            "columns": {
> >>> +                "name": {"type": "string"},
> >>> +                "filter": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> ["from-lport",
> >>> + "to-lport",
> >>> + "both"]]}}},
> >>> +                "sink":{"type": "string"},
> >>> +                "type": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set", ["gre",
> >>> + "erspan"]]}}},
> >>> +                "index": {"type": "integer"},
> >>> +                "src": {"type": {"key": {"type": "uuid",
> >>> +                                           "refTable":
> >>> "Logical_Switch_Port",
> >>> +                                           "refType": "weak"},
> >>> +                                   "min": 0,
> >>> +                                   "max": "unlimited"}},
> >>> +                "external_ids": {
> >>> +                    "type": {"key": "string", "value": "string",
> >>> +                             "min": 0, "max": "unlimited"}}},
> >>> +            "indexes": [["name"]],
> >>> +            "isRoot": true},
> >>>           "Meter": {
> >>>               "columns": {
> >>>                   "name": {"type": "string"},
> >>> diff --git a/ovn-nb.xml b/ovn-nb.xml
> >>> index f41e9d7c0..d8730c8fc 100644
> >>> --- a/ovn-nb.xml
> >>> +++ b/ovn-nb.xml
> >>> @@ -1554,6 +1554,11 @@
> >>>         </column>
> >>>       </group>
> >>>
> >>> +    <column name="mirror_rules">
> >>> +        Mirror rules that apply to logical switch port which is the
> >>> source.
> >>> +        Please see the <ref table="Mirror"/> table.
> >>> +    </column>
> >>> +
> >>>       <column name="ha_chassis_group">
> >>>         References a row in the OVN Northbound database's
> >>>         <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> >>> @@ -2491,6 +2496,64 @@
> >>>       </column>
> >>>     </table>
> >>>
> >>> +  <table name="Mirror" title="Mirror Entry">
> >>> +    <p>
> >>> +      Each row in this table represents one Mirror that can be used
> >>> for
> >>> +      port mirroring. These Mirrors are referenced by the
> >>> +      <ref column="mirror_rules" table="Logical_Switch_Port"/>
> >>> column in
> >>> +      the <ref table="Logical_Switch_Port"/> table.
> >>> +    </p>
> >>> +
> >>> +    <column name="name">
> >>> +      <p>
> >>> +        Represents the name of the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="filter">
> >>> +      <p>
> >>> +        The value of this field represents selection criteria of
> >>> the mirror.
> >>> +        Supported values for filter to-lport / from-lport / both
> >>> +        to-lport - to mirror packets coming into logical port
> >>> +        from-lport - to mirror packets going out of logical port
> >>> +        both - to mirror packets coming into and going out of
> >>> logical port.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="sink">
> >>> +      <p>
> >>> +        The value of this field represents the destination/sink of
> >>> the mirror.
> >>> +        The value it takes is an IP address of the sink port.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="type">
> >>> +      <p>
> >>> +        The value of this field represents the type of the tunnel
> >>> used for
> >>> +        sending the mirrored packets. Supported Tunnel types gre
> >>> and erspan
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="index">
> >>> +      <p>
> >>> +        The value of this field represents the tunnel ID. Depending
> >>> on the
> >>> +        tunnel type configured, GRE key value if type GRE and
> >>> erspan_idx value
> >>> +        if ERSPAN
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="src">
> >>> +      <p>
> >>> +        The value of this field represents a list of source ports
> >>> for the
> >>> +        mirror. Please see the <ref table="Logical_Switch_Port"/>
> >>> table.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="external_ids">
> >>> +      See <em>External IDs</em> at the beginning of this document.
> >>> +    </column>
> >>> +  </table>
> >>> +
> >>>     <table name="Meter" title="Meter entry">
> >>>       <p>
> >>>         Each row in this table represents a meter that can be used
> >>> for QoS or
> >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> >>> index 576ebbdeb..b83134416 100644
> >>> --- a/ovn-sb.ovsschema
> >>> +++ b/ovn-sb.ovsschema
> >>> @@ -1,7 +1,7 @@
> >>>   {
> >>>       "name": "OVN_Southbound",
> >>> -    "version": "20.25.0",
> >>> -    "cksum": "53184112 28845",
> >>> +    "version": "20.26.0",
> >>> +    "cksum": "2344151793 30004",
> >>>       "tables": {
> >>>           "SB_Global": {
> >>>               "columns": {
> >>> @@ -142,6 +142,23 @@
> >>>               "indexes": [["datapath", "tunnel_key"],
> >>>                           ["datapath", "name"]],
> >>>               "isRoot": true},
> >>> +        "Mirror": {
> >>> +            "columns": {
> >>> +                "name": {"type": "string"},
> >>> +                "filter": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> + ["from-lport",
> >>> + "to-lport","both"]]}}},
> >>> +                "sink":{"type": "string"},
> >>> +                "type": {"type": {"key": {"type": "string",
> >>> +                                            "enum": ["set",
> >>> +                                                     ["gre",
> >>> "erspan"]]}}},
> >>> +                "index": {"type": "integer"},
> >>> +                "external_ids": {
> >>> +                    "type": {"key": "string", "value": "string",
> >>> +                             "min": 0, "max": "unlimited"}}},
> >>> +            "indexes": [["name"]],
> >>> +            "isRoot": true},
> >>>           "Meter": {
> >>>               "columns": {
> >>>                   "name": {"type": "string"},
> >>> @@ -230,6 +247,11 @@
> >>> "refTable": "Encap",
> >>> "refType": "weak"},
> >>>                                       "min": 0, "max": "unlimited"}},
> >>> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> >>> +                                          "refTable": "Mirror",
> >>> +                                          "refType": "weak"},
> >>> +                                  "min": 0,
> >>> +                                  "max": "unlimited"}},
> >>>                   "mac": {"type": {"key": "string",
> >>>                                    "min": 0,
> >>>                                    "max": "unlimited"}},
> >>> diff --git a/ovn-sb.xml b/ovn-sb.xml
> >>> index 315d60853..05c0db6b4 100644
> >>> --- a/ovn-sb.xml
> >>> +++ b/ovn-sb.xml
> >>> @@ -2742,6 +2742,51 @@ tcp.flags = RST;
> >>>       </column>
> >>>     </table>
> >>>
> >>> +  <table name="Mirror" title="Mirror Entry">
> >>> +    <p>
> >>> +      Each row in this table represents one Mirror that can be used
> >>> for
> >>> +      port mirroring. These Mirrors are referenced by the
> >>> +      <ref column="mirror_rules" table="Port_Binding"/> column in
> >>> +      the <ref table="Port_Binding"/> table.
> >>> +    </p>
> >>> +
> >>> +    <column name="name">
> >>> +      <p>
> >>> +        Represents the name of the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="filter">
> >>> +      <p>
> >>> +        The value of this field represents selection criteria of
> >>> the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="sink">
> >>> +      <p>
> >>> +        The value of this field represents the destination/sink of
> >>> the mirror.
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="type">
> >>> +      <p>
> >>> +        The value of this field represents the type of the tunnel
> >>> used for
> >>> +        sending the mirrored packets
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="index">
> >>> +      <p>
> >>> +        The value of this field represents the key/idx depending on
> >>> the
> >>> +        tunnel type configured
> >>> +      </p>
> >>> +    </column>
> >>> +
> >>> +    <column name="external_ids">
> >>> +      See <em>External IDs</em> at the beginning of this document.
> >>> +    </column>
> >>> +  </table>
> >>> +
> >>>     <table name="Meter" title="Meter entry">
> >>>       <p>
> >>>         Each row in this table represents a meter that can be used
> >>> for QoS or
> >>> @@ -3244,6 +3289,11 @@ tcp.flags = RST;
> >>>         </column>
> >>>       </group>
> >>>
> >>> +    <column name="mirror_rules">
> >>> +        Mirror rules that apply to the port binding.
> >>> +        Please see the <ref table="Mirror"/> table.
> >>> +    </column>
> >>> +
> >>>       <group title="Patch Options">
> >>>         <p>
> >>>           These options apply to logical ports with <ref
> >>> column="type"/> of
> >>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> >>> index 4d480e357..d79f9d929 100644
> >>> --- a/tests/ovn-nbctl.at
> >>> +++ b/tests/ovn-nbctl.at
> >>> @@ -435,6 +435,126 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
> >>>
> >>>   dnl
> >>> ---------------------------------------------------------------------
> >>>
> >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> >>> +AT_CHECK([ovn-nbctl ls-add sw0])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> >>> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> >>> +
> >>> +dnl Add duplicate mirror name
> >>> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport
> >>> 10.10.10.5], [1], [], [stderr])
> >>> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach invalid source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [],
> >>> [stderr])
> >>> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach source port to invalid mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [],
> >>> [stderr])
> >>> +AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
> >>> +
> >>> +dnl Attach source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> >>> +
> >>> +dnl Attach one more source port to mirror
> >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> >>> +
> >>> +dnl Verify if multiple ports are attached to the same mirror properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +mirror3:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.3
> >>> +  Filter   :  to-lport
> >>> +  Index/Key:  2
> >>> +  Sources  :  sw0-port1  sw0-port3
> >>> +])
> >>> +
> >>> +dnl Detach one source port from mirror
> >>> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> >>> +
> >>> +dnl Verify if detach source port from mirror happens properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +mirror3:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.3
> >>> +  Filter   :  to-lport
> >>> +  Index/Key:  2
> >>> +  Sources  :  sw0-port1
> >>> +])
> >>> +
> >>> +dnl Delete a single mirror which has source attached.
> >>> +AT_CHECK([ovn-nbctl mirror-del mirror3])
> >>> +
> >>> +dnl Check if the detach happened from source properly
> >>> +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules
> >>> |  cut -b 3], [0], [dnl
> >>> +
> >>> +])
> >>> +
> >>> +dnl Check if the mirror deleted properly
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  10.10.10.1
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +mirror2:
> >>> +  Type     :  erspan
> >>> +  Sink     :  10.10.10.2
> >>> +  Filter   :  both
> >>> +  Index/Key:  1
> >>> +  Sources  :  None attached
> >>> +])
> >>> +
> >>> +dnl Delete another mirror
> >>> +AT_CHECK([ovn-nbctl mirror-del mirror2])
> >>> +
> >>> +dnl Update the Sink address
> >>> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> >>> +
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +mirror1:
> >>> +  Type     :  gre
> >>> +  Sink     :  192.168.1.13
> >>> +  Filter   :  from-lport
> >>> +  Index/Key:  0
> >>> +  Sources  :  None attached
> >>> +])
> >>> +
> >>> +dnl Delete all mirrors
> >>> +AT_CHECK([ovn-nbctl mirror-del])
> >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> >>> +])])
> >>> +
> >>> +dnl
> >>> ---------------------------------------------------------------------
> >>> +
> >>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
> >>>   AT_CHECK([ovn-nbctl lr-add lr0])
> >>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2],
> >>> [1], [],
> >>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> >>> index 4f399eccb..4e6c268e4 100644
> >>> --- a/tests/ovn-northd.at
> >>> +++ b/tests/ovn-northd.at
> >>> @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1}
> >>> meter_me__${acl2}
> >>>   AT_CLEANUP
> >>>   ])
> >>>
> >>> +OVN_FOR_EACH_NORTHD_NO_HV([
> >>> +AT_SETUP([Check NB-SB mirrors sync])
> >>> +AT_KEYWORDS([mirrors])
> >>> +ovn_start
> >>> +
> >>> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"10.10.10.2"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +erspan
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . sink=192.168.1.13
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +erspan
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . type=gre
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +0
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . index=12
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +12
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +both
> >>> +])
> >>> +
> >>> +check ovn-nbctl --wait=sb \
> >>> +    -- set mirror . filter=to-lport
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> >>> +to-lport
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> >>> +12
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> >>> +gre
> >>> +])
> >>> +
> >>> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> >>> +"192.168.1.13"
> >>> +])
> >>> +
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>>   OVN_FOR_EACH_NORTHD_NO_HV([
> >>>   AT_SETUP([ACL skip hints for stateless config])
> >>>   AT_KEYWORDS([acl])
> >>> diff --git a/tests/ovn.at b/tests/ovn.at
> >>> index f8b8db4df..cd5527ea1 100644
> >>> --- a/tests/ovn.at
> >>> +++ b/tests/ovn.at
> >>> @@ -16121,6 +16121,784 @@ OVN_CLEANUP([hv1], [hv2])
> >>>   AT_CLEANUP
> >>>   ])
> >>>
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror])
> >>> +AT_KEYWORDS([Mirror])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> >>> +    options:tx_pcap=hv1/vif1-tx.pcap \
> >>> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> >>> +    ofport-request=1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> >>> +    options:tx_pcap=hv1/vif2-tx.pcap \
> >>> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> >>> +    ofport-request=1
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +ovn-nbctl dump-flows > sbflows
> >>> +AT_CAPTURE_FILE([sbflows])
> >>> +
> >>> +for i in 1 2; do
> >>> +    : > vif$i.expected
> >>> +done
> >>> +
> >>> +net_add n2
> >>> +
> >>> +sim_add hv2
> >>> +as hv2
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:02:02:00\"
> >>> +ovn_attach n2 br-phys 192.168.1.12
> >>> +
> >>> +OVN_POPULATE_ARP
> >>> +
> >>> +as hv1
> >>> +
> >>> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST
> >>> IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> >>> +#
> >>> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> >>> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> >>> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> >>> +# provided, then it should be the ip and icmp checksums of the packet
> >>> +# responded; otherwise, no reply is expected.
> >>> +# In the absence of an ip checksum calculation helpers, this relies
> >>> +# on the caller to provide the checksums for the ip and icmp headers.
> >>> +# XXX This should be more systematic.
> >>> +#
> >>> +# INPORT is an lport number, e.g. 11 for vif11.
> >>> +# ETH_SRC and ETH_DST are each 12 hex digits.
> >>> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> >>> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> >>> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> >>> +# ENCAP_TYPE - gre or erspan
> >>> +# FILTER - Mirror Filter - to-lport / from-lport
> >>> +test_ipv4_icmp_request() {
> >>> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5
> >>> ip_chksum=$6 icmp_chksum=$7
> >>> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9
> >>> mirror_encap_type=${10} mirror_filter=${11}
> >>> +    shift; shift; shift; shift; shift; shift; shift
> >>> +    shift; shift; shift; shift;
> >>> +
> >>> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> >>> +    local ip_ttl=02
> >>> +    local icmp_id=5fbf
> >>> +    local icmp_seq=0001
> >>> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> >>> +    local icmp_type_code_request=0800
> >>> +    local
> >>> icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> >>> +    local
> >>> packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> >>> +
> >>> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> >>> +
> >>> +    # Expect to receive the reply, if any. In same port where
> >>> packet was sent.
> >>> +    # Note: src and dst fields are expected to be reversed.
> >>> +    local icmp_type_code_response=0000
> >>> +    local reply_icmp_ttl=fe
> >>> +    local
> >>> reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> >>> +    local
> >>> reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> >>> +    echo $reply >> vif$inport.expected
> >>> +    local remote_mac=000000020200
> >>> +    local local_mac=000000010200
> >>> +    if test ${mirror_encap_type} = "gre" ; then
> >>> +        local
> >>> ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> >>> +        if test ${mirror_filter} = "to-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> >>> +        elif test ${mirror_filter} = "from-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> >>> +        fi
> >>> +    elif test ${mirror_encap_type} = "erspan" ; then
> >>> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> >>> +        local erspan_seq0=100088be000000001000000000000000
> >>> +        local erspan_seq1=100088be000000011000000000000000
> >>> +        if test ${mirror_filter} = "to-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> >>>
> >>> +        elif test ${mirror_filter} = "from-lport" ; then
> >>> +            local
> >>> mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> >>> +        fi
> >>> +    fi
> >>> +    echo $mirror >> br-phys_n1.expected
> >>> +
> >>> +}
> >>> +
> >>> +# Set IPs
> >>> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> >>> +l1_ip=$(ip_to_hex 192 168 1 2)
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the reply
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . type=erspan
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the reply
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . filter=from-lport
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the request
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +as hv1 reset_pcap_file vif1 hv1/vif1
> >>> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >>> +rm -f br-phys_n1.expected
> >>> +rm -f vif1.expected
> >>> +
> >>> +check ovn-nbctl set mirror . type=gre
> >>> +
> >>> +# Send ping packet and check for mirrored packet of the request
> >>> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip
> >>> $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> >>> +
> >>> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> >>> [br-phys_n1.expected])
> >>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> >>> +
> >>> +echo "---------OVN NB Mirror-----"
> >>> +ovn-nbctl mirror-list
> >>> +
> >>> +echo "---------OVS Mirror----"
> >>> +ovs-vsctl list Mirror
> >>> +
> >>> +echo "-----------------------"
> >>> +
> >>> +echo "Verifying Mirror deletion in OVS"
> >>> +# Set vif1 iface-id such that OVN releases port binding
> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> >>> +])
> >>> +
> >>> +# Set vif1 iface-id back to ls1-lp1
> >>> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) =
> >>> "mirror0"])
> >>> +
> >>> +# Delete vif1 so that OVN releases port binding
> >>> +check ovs-vsctl del-port br-int vif1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> >>> +
> >>> +OVN_CLEANUP([hv1], [hv2])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk swap attachments])
> >>> +AT_KEYWORDS([Mirror test bulk swap attachments])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +# Equal detaches and attaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk attach multiple])
> >>> +AT_KEYWORDS([Mirror test bulk attach multiple])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-del
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +
> >>> +# Attaches multiple mirrors
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk more detach and less attach])
> >>> +AT_KEYWORDS([Mirror test bulk more detach and less attach])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl --wait=hv sync
> >>> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl mirror-del
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Detaches more than attaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$origB" = "$new2"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk attach more than detach])
> >>> +AT_KEYWORDS([Mirror test bulk attach more than detach])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Attaches more than detaches
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +as hv1
> >>> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> >>> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> >>> +
> >>> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> >>> +
> >>> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>> +
> >>> +OVN_FOR_EACH_NORTHD([
> >>> +AT_SETUP([Mirror test bulk detach multiple])
> >>> +AT_KEYWORDS([Mirror test bulk detach multiple])
> >>> +ovn_start
> >>> +
> >>> +# Logical network:
> >>> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> >>> +# and has switch ls2 (172.16.1.0/24) connected to it.
> >>> +
> >>> +ovn-nbctl lr-add R1
> >>> +
> >>> +ovn-nbctl ls-add ls1
> >>> +ovn-nbctl ls-add ls2
> >>> +
> >>> +# Connect ls1 to R1
> >>> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> >>> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> >>> +    type=router options:router-port=ls1
> >>> addresses=\"00:00:00:01:02:f1\"
> >>> +
> >>> +# Connect ls2 to R1
> >>> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> >>> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> >>> +    type=router options:router-port=ls2
> >>> addresses=\"00:00:00:01:02:f2\"
> >>> +
> >>> +# Create logical port ls1-lp1 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >>> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> >>> +
> >>> +# Create logical port ls1-lp2 in ls1
> >>> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> >>> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> >>> +
> >>> +# Create logical port ls2-lp1 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> >>> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> >>> +
> >>> +# Create logical port ls2-lp2 in ls2
> >>> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> >>> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> >>> +
> >>> +ovn-nbctl lsp-add ls1 ln-public
> >>> +ovn-nbctl lsp-set-type ln-public localnet
> >>> +ovn-nbctl lsp-set-addresses ln-public unknown
> >>> +ovn-nbctl lsp-set-options ln-public network_name=public
> >>> +
> >>> +# Create one hypervisor and create OVS ports corresponding to
> >>> logical ports.
> >>> +net_add n1
> >>> +
> >>> +sim_add hv1
> >>> +as hv1
> >>> +ovs-vsctl add-br br-phys -- set bridge br-phys
> >>> other-config:hwaddr=\"00:00:00:01:02:00\"
> >>> +ovn_attach n1 br-phys 192.168.1.11
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif1 -- \
> >>> +    set interface vif1 external-ids:iface-id=ls1-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif2 -- \
> >>> +    set interface vif2 external-ids:iface-id=ls2-lp1
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif3 -- \
> >>> +    set interface vif3 external-ids:iface-id=ls1-lp2
> >>> +
> >>> +ovs-vsctl -- add-port br-int vif4 -- \
> >>> +    set interface vif4 external-ids:iface-id=ls2-lp2
> >>> +
> >>> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> >>> +
> >>> +# Allow some time for ovn-northd and ovn-controller to catch up.
> >>> +wait_for_ports_up
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> >>> +
> >>> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> >>> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> >>> +
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +# Detaches all
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> >>> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> >>> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> >>> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> >>> +check ovn-nbctl --wait=hv sync
> >>> +
> >>> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> >>> +
> >>> +OVN_CLEANUP([hv1])
> >>> +AT_CLEANUP
> >>> +])
> >>>
> >>>   OVN_FOR_EACH_NORTHD([
> >>>   AT_SETUP([Port Groups])
> >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> >>> index 811468dc6..af2e61435 100644
> >>> --- a/utilities/ovn-nbctl.c
> >>> +++ b/utilities/ovn-nbctl.c
> >>> @@ -271,6 +271,19 @@ QoS commands:\n\
> >>>                               remove QoS rules from SWITCH\n\
> >>>     qos-list SWITCH           print QoS rules for SWITCH\n\
> >>>   \n\
> >>> +Mirror commands:\n\
> >>> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> >>> +                            add a mirror with given name\n\
> >>> +                            specify TYPE 'gre' or 'erspan'\n\
> >>> +                            specify the tunnel INDEX value\n\
> >>> +                                (indicates key if GRE\n\
> >>> +                                 erpsan_idx if ERSPAN)\n\
> >>> +                            specify FILTER for mirroring selection\n\
> >>> +                                'to-lport' / 'from-lport' / 'both'\n\
> >>> +                            specify Sink / Destination i.e. Remote
> >>> IP\n\
> >>> +  mirror-del [NAME]         remove mirrors\n\
> >>> +  mirror-list               print mirrors\n\
> >>> +\n\
> >>>   Meter commands:\n\
> >>>     [--fair]\n\
> >>>     meter-add NAME ACTION RATE UNIT [BURST]\n\
> >>> @@ -311,6 +324,8 @@ Logical switch port commands:\n\
> >>>                               set dhcpv6 options for PORT\n\
> >>>     lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
> >>>     lsp-get-ls PORT           get the logical switch which the port
> >>> belongs to\n\
> >>> +  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
> >>> +  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
> >>>   \n\
> >>>   Forwarding group commands:\n\
> >>>     [--liveness]\n\
> >>> @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_type);
> >>>   }
> >>>
> >>> +static void
> >>> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> + &nbrec_logical_switch_port_col_mirror_rules);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static int
> >>> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> >>> +{
> >>> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> >>> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> >>> +
> >>> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> >>> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> >>> +
> >>> +    return strcmp(mirror1->name,mirror2->name);
> >>> +}
> >>> +
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> >>> +                    bool must_exist,
> >>> +                    const struct nbrec_mirror **mirror_p)
> >>> +{
> >>> +    const struct nbrec_mirror *mirror = NULL;
> >>> +    *mirror_p = NULL;
> >>> +
> >>> +    struct uuid mirror_uuid;
> >>> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> >>> +    if (is_uuid) {
> >>> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> >>> +    }
> >>> +
> >>> +    if (!mirror) {
> >>> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +            if (!strcmp(mirror->name, id)) {
> >>> +                break;
> >>> +            }
> >>> +        }
> >>> +    }
> >>> +
> >>> +    if (!mirror && must_exist) {
> >>> +        return xasprintf("%s: mirror %s not found",
> >>> +                         id, is_uuid ? "UUID" : "name");
> >>> +    }
> >>> +
> >>> +    *mirror_p = mirror;
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *port = ctx->argv[1];
> >>> +    const char *mirror_name = ctx->argv[2];
> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +
> >>> +    char *error;
> >>> +
> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +
> >>> +    /*check if a mirror rule actually exists on that name or not*/
> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Check if same mirror rule already exists for the lsp */
> >>> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> >>> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> >>> +            bool may_exist = shash_find(&ctx->options,
> >>> "--may-exist") != NULL;
> >>> +            if (!may_exist) {
> >>> +                ctl_error(ctx, "Same mirror already existed on the
> >>> lsp %s.",
> >>> +                          ctx->argv[1]);
> >>> +                return;
> >>> +            }
> >>> +            return;
> >>> +        }
> >>> +    }
> >>> +
> >>> + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> >>> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *port = ctx->argv[1];
> >>> +    const char *mirror_name = ctx->argv[2];
> >>> +    const struct nbrec_logical_switch_port *lsp = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +
> >>> +    char *error;
> >>> +
> >>> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +
> >>> +    /*check if a mirror rule actually exists on that name or not*/
> >>> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> >>> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> >>> +
> >>> +}
> >>> +
> >>>   static void
> >>>   nbctl_lsp_set_type(struct ctl_context *ctx)
> >>>   {
> >>> @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct
> >>> ctl_context *ctx)
> >>>       nbrec_ha_chassis_set_priority(ha_chassis, priority);
> >>>   }
> >>>
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +parse_filter(const char *arg, const char **selection_p)
> >>> +{
> >>> +    /* Validate selection.  Only require the first letter. */
> >>> +    if (arg[0] == 't') {
> >>> +        *selection_p = "to-lport";
> >>> +    } else if (arg[0] == 'f') {
> >>> +        *selection_p = "from-lport";
> >>> +    } else if (arg[0] == 'b') {
> >>> +        *selection_p = "both";
> >>> +    } else {
> >>> +        *selection_p = NULL;
> >>> +        return xasprintf("%s: selection must be \"to-lport\" or "
> >>> +                         "\"from-lport\" or \"both\" ", arg);
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static char * OVS_WARN_UNUSED_RESULT
> >>> +parse_type(const char *arg, const char **type_p)
> >>> +{
> >>> +    /* Validate type.  Only require the first letter. */
> >>> +    if (arg[0] == 'g') {
> >>> +        *type_p = "gre";
> >>> +    } else if (arg[0] == 'e') {
> >>> +        *type_p = "erspan";
> >>> +    } else {
> >>> +        *type_p = NULL;
> >>> +        return xasprintf("%s: type must be \"gre\" or "
> >>> +                         "\"erspan\"", arg);
> >>> +    }
> >>> +    return NULL;
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_add(struct ctl_context *ctx)
> >>> +{
> >>> +    const char *filter = NULL;
> >>> +    const char *sink_ip = NULL;
> >>> +    const char *type = NULL;
> >>> +    const char *name = NULL;
> >>> +    char *new_sink_ip = NULL;
> >>> +    int64_t index;
> >>> +    char *error = NULL;
> >>> +    const struct nbrec_mirror *mirror_check = NULL;
> >>> +
> >>> +    /* Mirror Name */
> >>> +    name = ctx->argv[1];
> >>> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> >>> +        if (!strcmp(mirror_check->name, name)) {
> >>> +            ctl_error(ctx, "Mirror with %s name already exists.",
> >>> +                      name);
> >>> +            return;
> >>> +        }
> >>> +    }
> >>> +
> >>> +    /* Tunnel Type - GRE/ERSPAN */
> >>> +    error = parse_type(ctx->argv[2], &type);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* tunnel index / GRE key / ERSPAN idx */
> >>> +    index = atoi(ctx->argv[3]);
> >> Shouldn't we validate the input is an actual number?
> >>
> >>> +
> >>> +    /* Filter for mirroring */
> >>> +    error = parse_filter(ctx->argv[4], &filter);
> >>> +    if (error) {
> >>> +        ctx->error = error;
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Destination / Sink details */
> >>> +    sink_ip = ctx->argv[5];
> >>> +
> >>> +    /* check if it is a valid ip */
> >>> +    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
> >>> +    if (!new_sink_ip) {
> >>> +        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
> >>> +    }
> >>> +
> >>> +    if (new_sink_ip) {
> >>> +        free(new_sink_ip);
> >>> +    } else {
> >>> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Create the mirror. */
> >>> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> >>> +    nbrec_mirror_set_name(mirror, name);
> >>> +    nbrec_mirror_set_index(mirror, index);
> >>> +    nbrec_mirror_set_filter(mirror, filter);
> >>> +    nbrec_mirror_set_type(mirror, type);
> >>> +    nbrec_mirror_set_sink(mirror, sink_ip);
> >>> +
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_del(struct ctl_context *ctx)
> >>> +{
> >>> +    const struct nbrec_mirror *mirror, *next;
> >>> +
> >>> +    /* If a name is not specified, delete all mirrors. */
> >>> +    if (ctx->argc == 1) {
> >>> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> >>> +            nbrec_mirror_delete(mirror);
> >>> +        }
> >>> +        return;
> >>> +    }
> >>> +
> >>> +    /* Remove the matching mirror. */
> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +        if (strcmp(ctx->argv[1], mirror->name)) {
> >>> +            continue;
> >>> +        }
> >>> +        nbrec_mirror_delete(mirror);
> >>> +        return;
> >>> +    }
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> >>> +{
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &nbrec_logical_switch_port_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> >>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> >>> +}
> >>> +
> >>> +static void
> >>> +nbctl_mirror_list(struct ctl_context *ctx)
> >>> +{
> >>> +
> >>> +    const struct nbrec_mirror **mirrors = NULL;
> >>> +    const struct nbrec_mirror *mirror;
> >>> +    size_t n_capacity = 0;
> >>> +    size_t n_mirrors = 0;
> >>> +
> >>> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> >>> +        if (n_mirrors == n_capacity) {
> >>> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof
> >>> *mirrors);
> >>> +        }
> >>> +
> >>> +        mirrors[n_mirrors] = mirror;
> >>> +        n_mirrors++;
> >>> +    }
> >>> +
> >>> +    if (n_mirrors) {
> >>> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> >>> +    }
> >>> +
> >>> +    for (size_t i = 0; i < n_mirrors; i++) {
> >>> +        mirror = mirrors[i];
> >>> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> >>> +        /* print all the values */
> >>> +        ds_put_format(&ctx->output, "  Type     : %s\n",
> >>> mirror->type);
> >>> +        ds_put_format(&ctx->output, "  Sink     : %s\n",
> >>> mirror->sink);
> >>> +        ds_put_format(&ctx->output, "  Filter   : %s\n",
> >>> mirror->filter);
> >>> +        ds_put_format(&ctx->output, "  Index/Key: %ld\n",
> >>> +                                                (long int)
> >>> mirror->index);
> >> You don't ned to cast if you pass %d formatter instead of %ld. The
> >> same applies to other places in the patch where you cast to long int.
> >> In general, casting is not needed and should be avoided.
> >>
> >>> + ds_put_cstr(&ctx->output,   "  Sources  :");
> >>> +        if (mirror->n_src > 0) {
> >>> +            struct svec srcs;
> >>> +            const char *src;
> >>> +            size_t j;
> >>> +            svec_init(&srcs);
> >>> +            for (j = 0; j < mirror->n_src; j++) {
> >>> +                svec_add(&srcs, mirror->src[j]->name);
> >>> +            }
> >>> +            svec_sort(&srcs);
> >>> +            SVEC_FOR_EACH (j, src, &srcs) {
> >>> +                ds_put_format(&ctx->output, "  %s", src);
> >>> +            }
> >>> +            svec_destroy(&srcs);
> >>> +        } else {
> >>> +            ds_put_cstr(&ctx->output, "  None attached");
> >>> +        }
> >>> +        ds_put_cstr(&ctx->output, "\n");
> >>> +    }
> >>> +
> >>> +    free(mirrors);
> >>> +}
> >>> +
> >>>   static const struct ctl_table_class tables[NBREC_N_TABLES] = {
> >>>       [NBREC_TABLE_DHCP_OPTIONS].row_ids
> >>>       = {{&nbrec_logical_switch_port_col_name, NULL,
> >>> @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax
> >>> nbctl_commands[] = {
> >>>       { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
> >>>         NULL, "", RO },
> >>>
> >>> +    /* mirror commands. */
> >>> +    { "mirror-add", 5, 5,
> >>> +      "NAME TYPE INDEX FILTER IP",
> >>> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist",
> >>> RW },
> >>> +    { "mirror-del", 0, 1, "[NAME]",
> >>> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> >>> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list,
> >>> nbctl_mirror_list,
> >>> +      NULL, "", RO },
> >>> +
> >>>       /* meter commands. */
> >>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
> >>> nbctl_pre_meter_add,
> >>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> >>> @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax
> >>> nbctl_commands[] = {
> >>>         nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
> >>>       { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
> >>> nbctl_lsp_get_ls,
> >>>         NULL, "", RO },
> >>> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> >>> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> >>> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> >>> +      nbctl_lsp_detach_mirror, NULL, "", RW },
> >>>
> >>>       /* forwarding group commands. */
> >>>       { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> >>> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> >>> index f60dde1b6..3d73e9e25 100644
> >>> --- a/utilities/ovn-sbctl.c
> >>> +++ b/utilities/ovn-sbctl.c
> >>> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
> >>>       ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> >>> +    ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_port_binding_col_mirror_rules);
> >>>
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_logical_flow_col_logical_datapath);
> >>>       ovsdb_idl_add_column(ctx->idl,
> >>> &sbrec_logical_flow_col_logical_dp_group);
> >>> @@ -1431,6 +1432,9 @@ static const struct ctl_table_class
> >>> tables[SBREC_N_TABLES] = {
> >>>       [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
> >>>       = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
> >>>
> >>> +    [SBREC_TABLE_MIRROR].row_ids[0]
> >>> +    = {&sbrec_mirror_col_name, NULL, NULL},
> >>> +
> >>>       [SBREC_TABLE_METER].row_ids[0]
> >>>       = {&sbrec_meter_col_name, NULL, NULL},
> >>>
> >>> --
> >>> 2.31.1
> >>>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 224a7b83e..84b22abdb 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,7 @@  OVN v22.09.0 - 16 Sep 2022
     any of LR's LRP IP, there is no need to create SNAT entry.  Now such
     traffic destined to LRP IP is not dropped.
   - Bump python version required for building OVN to 3.6.
+  - Added Support for Remote Port Mirroring.
 
 OVN v22.06.0 - 03 Jun 2022
 --------------------------
diff --git a/controller/automake.mk b/controller/automake.mk
index c2ab1bbe6..334672b4d 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -41,7 +41,9 @@  controller_ovn_controller_SOURCES = \
 	controller/ovsport.h \
 	controller/ovsport.c \
 	controller/vif-plug.h \
-	controller/vif-plug.c
+	controller/vif-plug.c \
+	controller/mirror.h \
+	controller/mirror.c
 
 controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
 man_MANS += controller/ovn-controller.8
diff --git a/controller/mirror.c b/controller/mirror.c
new file mode 100644
index 000000000..11f2b63a6
--- /dev/null
+++ b/controller/mirror.c
@@ -0,0 +1,538 @@ 
+/* Copyright (c) 2022 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <unistd.h>
+
+/* library headers */
+#include "lib/sset.h"
+#include "lib/util.h"
+
+/* OVS includes. */
+#include "lib/vswitch-idl.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "binding.h"
+#include "lib/ovn-sb-idl.h"
+#include "mirror.h"
+
+VLOG_DEFINE_THIS_MODULE(port_mirror);
+
+/* Static function declarations */
+
+static const struct ovsrec_port *
+get_port_for_iface(const struct ovsrec_interface *iface,
+                  const struct ovsrec_bridge *br_int)
+{
+    for (size_t i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *p = br_int->ports[i];
+        for (size_t j = 0; j < p->n_interfaces; j++) {
+            if (!strcmp(iface->name, p->interfaces[j]->name)) {
+                return p;
+            }
+        }
+    }
+    return NULL;
+}
+
+static bool
+mirror_create(const struct sbrec_port_binding *pb,
+              struct port_mirror_ctx *pm_ctx)
+{
+    const struct ovsrec_mirror *mirror = NULL;
+
+    if (pb->n_up && !pb->up[0]) {
+        return true;
+    }
+
+    if (pb->chassis != pm_ctx->chassis_rec) {
+        return true;
+    }
+
+    if (!pm_ctx->ovs_idl_txn) {
+        return false;
+    }
+
+
+    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
+    /* Loop through the mirror rules */
+    for (size_t i =0; i < pb->n_mirror_rules; i++) {
+        /* check if the mirror already exists in OVS DB */
+        bool create_mirror = true;
+        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
+            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
+                /* Mirror with same name already exists
+                 * No need to create mirror
+                 */
+                create_mirror = false;
+                break;
+            }
+        }
+
+        if (create_mirror) {
+
+            struct smap options = SMAP_INITIALIZER(&options);
+            char *port_name, *key;
+
+            key = xasprintf("%ld",(long int) pb->mirror_rules[i]->index);
+            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
+            smap_add(&options, "key", key);
+            if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
+                /* Set the ERSPAN index */
+                smap_add(&options, "erspan_idx", key);
+                smap_add(&options, "erspan_ver","1");
+
+            }
+            struct ovsrec_interface *iface =
+                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
+            port_name = xasprintf("ovn-%s",
+                                   pb->mirror_rules[i]->name);
+
+            ovsrec_interface_set_name(iface, port_name);
+            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
+            ovsrec_interface_set_options(iface, &options);
+
+            struct ovsrec_port *port =
+                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
+            ovsrec_port_set_name(port, port_name);
+            ovsrec_port_set_interfaces(port, &iface, 1);
+
+            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
+
+            smap_destroy(&options);
+            free(port_name);
+            free(key);
+
+            VLOG_INFO("Creating Mirror in OVS DB");
+            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
+            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
+            ovsrec_mirror_update_output_port_addvalue(mirror, port);
+            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
+                                                             mirror);
+        }
+
+        struct local_binding *lbinding = local_binding_find(
+                               pm_ctx->local_bindings, pb->logical_port);
+        const struct ovsrec_port *p =
+                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
+        if (p) {
+            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
+                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
+            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
+                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
+            } else {
+                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
+                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
+            }
+        }
+    }
+    return true;
+}
+
+static void
+check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
+                              struct ovsrec_mirror *ovs_mirror)
+{
+    char *filter;
+    if ((ovs_mirror->n_select_dst_port)
+            && (ovs_mirror->n_select_src_port)) {
+        filter = "both";
+    } else if (ovs_mirror->n_select_dst_port) {
+        filter = "to-lport";
+    } else {
+        filter = "from-lport";
+    }
+
+    if (strcmp(sb_mirror->filter, filter)) {
+        if (!strcmp(sb_mirror->filter,"from-lport")
+                              && !strcmp(filter,"both")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"to-lport")
+                              && !strcmp(filter,"both")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"both")
+                              && !strcmp(filter,"from-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"both")
+                              && !strcmp(filter,"to-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"to-lport")
+                              && !strcmp(filter,"from-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"from-lport")
+                              && !strcmp(filter,"to-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        }
+    }
+}
+
+static void
+check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+                                   struct ovsrec_mirror *ovs_mirror)
+{
+    struct smap options = SMAP_INITIALIZER(&options);
+    char *key, *type;
+    struct ovsrec_interface *iface =
+                          ovs_mirror->output_port->interfaces[0];
+    struct smap *opts = &iface->options;
+
+    const char *erspan_ver = smap_get(opts, "erspan_ver");
+    if (erspan_ver) {
+        type = "erspan";
+    } else {
+        type = "gre";
+    }
+    if (strcmp(type, sb_mirror->type)) {
+        ovsrec_interface_set_type(iface, sb_mirror->type);
+    }
+
+    key = xasprintf("%ld",(long int) sb_mirror->index);
+    smap_add(&options, "remote_ip", sb_mirror->sink);
+    smap_add(&options, "key", key);
+
+    if (!strcmp(sb_mirror->type, "erspan")) {
+        /* Set the ERSPAN index */
+        smap_add(&options, "erspan_idx", key);
+        smap_add(&options, "erspan_ver","1");
+    }
+
+    ovsrec_interface_set_options(iface, &options);
+    smap_destroy(&options);
+    free(key);
+
+}
+
+static void
+mirror_update(const struct sbrec_mirror *sb_mirror,
+              struct ovsrec_mirror *ovs_mirror)
+{
+    check_and_update_interface_table(sb_mirror, ovs_mirror);
+
+    check_and_update_mirror_table(sb_mirror, ovs_mirror);
+}
+
+static bool
+mirror_delete(const struct sbrec_port_binding *pb,
+              struct port_mirror_ctx *pm_ctx,
+              struct shash *pb_mirror_map,
+              bool delete_all)
+{
+
+    if (!pm_ctx->ovs_idl_txn) {
+        return false;
+    }
+
+    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
+
+    if (!delete_all) {
+        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
+            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
+        }
+    }
+
+    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
+        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
+
+            struct ovsrec_mirror *ovs_mirror = NULL;
+            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
+                                            pb->mirror_rules[i]->name);
+            if (ovs_mirror) {
+                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                               ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                            ovs_mirror);
+                ovsrec_port_delete(ovs_mirror->output_port);
+                ovsrec_mirror_delete(ovs_mirror);
+            }
+        }
+    }
+
+    struct shash_node *mirror_node;
+    const struct sbrec_port_binding *sb_pb;
+    int attach_cnt = 0;
+    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
+        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
+        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
+            /* Find if the mirror has other sources */
+            attach_cnt = 0;
+            SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (sb_pb,
+                                       pm_ctx->port_binding_table) {
+                for (size_t i = 0; i < sb_pb->n_mirror_rules; i++) {
+                    if (!strcmp(sb_pb->mirror_rules[i]->name,
+                                                ovs_mirror->name)) {
+                        attach_cnt++;
+                    }
+                }
+            }
+            if (attach_cnt) {
+                /* More than 1 source then just
+                 * update the mirror table
+                 */
+                bool done = false;
+                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
+                                                   && (done == false)); i++) {
+                    const struct ovsrec_port *port_rec =
+                                               ovs_mirror->select_dst_port[i];
+                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                        const struct ovsrec_interface *iface_rec;
+
+                        iface_rec = port_rec->interfaces[j];
+                        const char *iface_id =
+                                            smap_get(&iface_rec->external_ids,
+                                                                  "iface-id");
+                        if (!strcmp(iface_id,pb->logical_port)) {
+                            ovsrec_mirror_update_select_dst_port_delvalue(
+                                                        ovs_mirror, port_rec);
+                            done = true;
+                            break;
+                        }
+                    }
+                }
+                done = false;
+                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
+                                                   && (done == false)); i++) {
+                    const struct ovsrec_port *port_rec =
+                                                ovs_mirror->select_src_port[i];
+                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                        const struct ovsrec_interface *iface_rec;
+
+                        iface_rec = port_rec->interfaces[j];
+                        const char *iface_id =
+                                            smap_get(&iface_rec->external_ids,
+                                                                  "iface-id");
+                        if (!strcmp(iface_id,pb->logical_port)) {
+                            ovsrec_mirror_update_select_src_port_delvalue(
+                                                        ovs_mirror, port_rec);
+                            done = true;
+                            break;
+                        }
+                    }
+                }
+            } else {
+                /*
+                 * If only 1 source delete the output port
+                 * and then delete the mirror completely
+                 */
+                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
+                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                                    ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                            ovs_mirror);
+                ovsrec_port_delete(ovs_mirror->output_port);
+                ovsrec_mirror_delete(ovs_mirror);
+            }
+        }
+    }
+
+    const char *used_node, *used_next;
+    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
+        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
+    }
+    sset_destroy(&pb_mirrors);
+
+    return true;
+}
+
+static void
+find_port_specific_mirrors (const struct sbrec_port_binding *pb,
+                            struct port_mirror_ctx *pm_ctx,
+                            struct shash *pb_mirror_map)
+{
+    const struct ovsrec_mirror *mirror = NULL;
+
+    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
+        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
+            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
+            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                const struct ovsrec_interface *iface_rec;
+                iface_rec = port_rec->interfaces[j];
+                const char *logical_port =
+                    smap_get(&iface_rec->external_ids, "iface-id");
+                if (!strcmp(logical_port, pb->logical_port)) {
+                    shash_add_once(pb_mirror_map, mirror->name, mirror);
+                }
+            }
+        }
+        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
+            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
+            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                const struct ovsrec_interface *iface_rec;
+                iface_rec = port_rec->interfaces[j];
+                const char *logical_port =
+                    smap_get(&iface_rec->external_ids, "iface-id");
+                if (!strcmp(logical_port, pb->logical_port)) {
+                    shash_add_once(pb_mirror_map, mirror->name, mirror);
+                }
+            }
+        }
+    }
+}
+
+void
+mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+{
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
+
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
+}
+
+
+void
+ovn_port_mirror_init(struct shash *ovs_mirrors)
+{
+    shash_init(ovs_mirrors);
+}
+
+void
+ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
+{
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
+                                       pm_ctx->port_binding_table) {
+        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
+    }
+}
+
+bool
+ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
+                     struct port_mirror_ctx *pm_ctx)
+{
+    bool ret = true;
+    struct local_binding *lbinding = local_binding_find(
+                               pm_ctx->local_bindings, pb->logical_port);
+
+    if (strcmp(pb->type, "") && (!lbinding)) {
+        return ret;
+    }
+
+    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
+
+    /* Need to find if mirror needs update */
+    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
+    if (!removed) {
+        if ((pb->n_mirror_rules == 0)
+              && (shash_is_empty(&port_ovs_mirrors))) {
+            /* No mirror update */
+        } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) {
+            /* Though number of mirror rules are same,
+             * need to verify the contents
+             */
+            for (size_t i = 0; i < pb->n_mirror_rules; i++) {
+                if (!shash_find(&port_ovs_mirrors,
+                               pb->mirror_rules[i]->name)) {
+                    /* Mis match in OVN SB DB and OVS DB
+                     * Delete and Create mirror(s) with proper sources
+                     */
+                    ret = mirror_delete(pb, pm_ctx,
+                                        &port_ovs_mirrors, false);
+                    if (ret) {
+                        ret = mirror_create(pb, pm_ctx);
+                    }
+                    break;
+                }
+            }
+        } else {
+            /* Update Mirror */
+            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
+                /* create mirror,
+                 * if mirror already exists only update selection
+                 */
+                ret = mirror_create(pb, pm_ctx);
+            } else {
+                /* delete mirror,
+                 * if mirror has other sources only update selection
+                 */
+                ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
+            }
+        }
+    } else {
+        ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
+    }
+
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                                              &port_ovs_mirrors) {
+        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
+    }
+    shash_destroy(&port_ovs_mirrors);
+
+    return ret;
+}
+
+bool
+ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
+{
+    const struct sbrec_mirror *mirror = NULL;
+    struct ovsrec_mirror *ovs_mirror = NULL;
+
+    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
+    /* For each tracked mirror entry check if OVS entry is there*/
+        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
+        if (ovs_mirror) {
+            if (sbrec_mirror_is_deleted(mirror)) {
+                /* Need to delete the mirror in OVS */
+                VLOG_INFO("Delete mirror and remove port");
+                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                                    ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                      ovs_mirror);
+                ovsrec_port_delete(ovs_mirror->output_port);
+                ovsrec_mirror_delete(ovs_mirror);
+            } else {
+                mirror_update(mirror, ovs_mirror);
+            }
+        }
+    }
+
+    return true;
+}
+
+void
+ovn_port_mirror_destroy(struct shash *ovs_mirrors)
+{
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                                              ovs_mirrors) {
+        shash_delete(ovs_mirrors, ovs_mirror_node);
+    }
+    shash_destroy(ovs_mirrors);
+}
diff --git a/controller/mirror.h b/controller/mirror.h
new file mode 100644
index 000000000..85b964f55
--- /dev/null
+++ b/controller/mirror.h
@@ -0,0 +1,53 @@ 
+/* Copyright (c) 2022 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_MIRROR_H
+#define OVN_MIRROR_H 1
+
+struct ovsdb_idl_txn;
+struct ovsrec_port_table;
+struct ovsrec_bridge;
+struct ovsrec_bridge_table;
+struct ovsrec_open_vswitch_table;
+struct sbrec_chassis;
+struct ovsrec_interface_table;
+struct ovsrec_mirror_table;
+struct sbrec_mirror_table;
+struct sbrec_port_binding_table;
+
+struct port_mirror_ctx {
+    struct shash *ovs_mirrors;
+    struct ovsdb_idl_txn *ovs_idl_txn;
+    const struct ovsrec_port_table *port_table;
+    const struct ovsrec_bridge *br_int;
+    const struct sbrec_chassis *chassis_rec;
+    const struct ovsrec_bridge_table *bridge_table;
+    const struct ovsrec_open_vswitch_table *ovs_table;
+    const struct ovsrec_interface_table *iface_table;
+    const struct ovsrec_mirror_table *mirror_table;
+    const struct sbrec_mirror_table *sb_mirror_table;
+    const struct sbrec_port_binding_table *port_binding_table;
+    struct shash *local_bindings;
+};
+
+void mirror_register_ovs_idl(struct ovsdb_idl *);
+void ovn_port_mirror_init(struct shash *);
+void ovn_port_mirror_destroy(struct shash *);
+void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
+bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
+                                  bool removed,
+                                  struct port_mirror_ctx *pm_ctx);
+bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
+#endif
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 8895c7a2b..15ab17c4a 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -78,6 +78,7 @@ 
 #include "lib/inc-proc-eng.h"
 #include "lib/ovn-l7.h"
 #include "hmapx.h"
+#include "mirror.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -966,6 +967,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
+    mirror_register_ovs_idl(ovs_idl);
 }
 
 #define SB_NODES \
@@ -986,6 +988,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     SB_NODE(load_balancer, "load_balancer") \
     SB_NODE(fdb, "fdb") \
     SB_NODE(meter, "meter") \
+    SB_NODE(mirror, "mirror") \
     SB_NODE(static_mac_binding, "static_mac_binding")
 
 enum sb_engine_node {
@@ -1003,7 +1006,8 @@  enum sb_engine_node {
     OVS_NODE(bridge, "bridge") \
     OVS_NODE(port, "port") \
     OVS_NODE(interface, "interface") \
-    OVS_NODE(qos, "qos")
+    OVS_NODE(qos, "qos") \
+    OVS_NODE(mirror, "mirror")
 
 enum ovs_engine_node {
 #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
@@ -2383,6 +2387,203 @@  load_balancers_by_dp_cleanup(struct hmap *lbs)
     free(lbs);
 }
 
+/* Mirror Engine */
+struct ed_type_port_mirror {
+    struct shash ovs_mirrors;
+};
+
+static void *
+en_port_mirror_init(struct engine_node *node OVS_UNUSED,
+                    struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
+    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
+    return port_mirror;
+}
+
+static void
+en_port_mirror_cleanup(void *data)
+{
+    struct ed_type_port_mirror *port_mirror = data;
+    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
+}
+
+static void
+init_port_mirror_ctx(struct engine_node *node,
+                 struct ed_type_runtime_data *rt_data,
+                 struct ed_type_port_mirror *port_mirror_data,
+                 struct port_mirror_ctx *pm_ctx)
+{
+    struct ovsrec_open_vswitch_table *ovs_table =
+        (struct ovsrec_open_vswitch_table *) EN_OVSDB_GET(
+            engine_get_input("OVS_open_vswitch", node));
+    struct ovsrec_bridge_table *bridge_table =
+        (struct ovsrec_bridge_table *) EN_OVSDB_GET(
+            engine_get_input("OVS_bridge", node));
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
+
+    ovs_assert(br_int && chassis_id);
+    const struct sbrec_chassis *chassis = NULL;
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+
+    if (chassis_id) {
+        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    }
+    ovs_assert(chassis);
+
+    struct ovsrec_port_table *port_table =
+        (struct ovsrec_port_table *) EN_OVSDB_GET(
+            engine_get_input("OVS_port", node));
+
+    struct ed_type_ovs_interface_shadow *iface_shadow =
+        engine_get_input_data("ovs_interface_shadow", node);
+
+    struct ovsrec_mirror_table *mirror_table =
+        (struct ovsrec_mirror_table *) EN_OVSDB_GET(
+            engine_get_input("OVS_mirror", node));
+
+    struct sbrec_port_binding_table *pb_table =
+        (struct sbrec_port_binding_table *) EN_OVSDB_GET(
+            engine_get_input("SB_port_binding", node));
+
+    struct sbrec_mirror_table *sb_mirror_table =
+        (struct sbrec_mirror_table *) EN_OVSDB_GET(
+            engine_get_input("SB_mirror", node));
+
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                           &port_mirror_data->ovs_mirrors) {
+        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
+    }
+
+    const struct ovsrec_mirror *ovsmirror = NULL;
+    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
+       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
+    }
+
+    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
+    pm_ctx->port_table = port_table;
+    pm_ctx->iface_table = iface_shadow->iface_table;
+    pm_ctx->mirror_table = mirror_table;
+    pm_ctx->port_binding_table = pb_table;
+    pm_ctx->sb_mirror_table = sb_mirror_table;
+    pm_ctx->br_int = br_int;
+    pm_ctx->chassis_rec = chassis;
+    pm_ctx->bridge_table = bridge_table;
+    pm_ctx->ovs_table = ovs_table;
+    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
+    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
+}
+
+static void
+en_port_mirror_run(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    ovn_port_mirror_run(&pm_ctx);
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+static bool
+port_mirror_runtime_data_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    /* There is no tracked data. Fall back to full recompute of port_mirror */
+    if (!rt_data->tracked) {
+        return false;
+    }
+
+    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    if (hmap_is_empty(tracked_dp_bindings)) {
+        return true;
+    }
+
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    struct tracked_datapath *tdp;
+    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
+        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
+            /* Fall back to full recompute when a local datapath
+             * is added or deleted. */
+            return false;
+        }
+
+        struct shash_node *shash_node;
+        SHASH_FOR_EACH (shash_node, &tdp->lports) {
+            struct tracked_lport *lport = shash_node->data;
+            bool removed =
+                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
+            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
+                return false;
+            }
+        }
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
+static bool
+port_mirror_port_binding_handler(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    /* handle port binding updates (i.,e when the mirror column
+     * of port_binding is updated)
+     */
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
+                                               pm_ctx.port_binding_table) {
+        bool removed = sbrec_port_binding_is_deleted(pb);
+        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
+            return false;
+        }
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+
+}
+
+static bool
+port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    /* handle sb mirror updates
+     */
+    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
+        return false;
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+
+}
+
 /* Engine node which is used to handle the Non VIF data like
  *   - OVS patch ports
  *   - Tunnel ports and the related chassis information.
@@ -3704,6 +3905,7 @@  main(int argc, char *argv[])
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
     ENGINE_NODE(northd_options, "northd_options");
     ENGINE_NODE(dhcp_options, "dhcp_options");
+    ENGINE_NODE(port_mirror, "port_mirror");
 
 #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
     SB_NODES
@@ -3862,6 +4064,22 @@  main(int argc, char *argv[])
     engine_add_input(&en_flow_output, &en_pflow_output,
                      flow_output_pflow_output_handler);
 
+    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
+    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
+    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
+                     engine_noop_handler);
+    engine_add_input(&en_flow_output, &en_port_mirror,
+                     engine_noop_handler);
+    engine_add_input(&en_port_mirror, &en_runtime_data,
+                     port_mirror_runtime_data_handler);
+    engine_add_input(&en_port_mirror, &en_sb_mirror,
+                     port_mirror_sb_mirror_handler);
+    engine_add_input(&en_port_mirror, &en_sb_port_binding,
+                     port_mirror_port_binding_handler);
+
     struct engine_arg engine_arg = {
         .sb_idl = ovnsb_idl_loop.idl,
         .ovs_idl = ovs_idl_loop.idl,
@@ -4131,34 +4349,36 @@  main(int argc, char *argv[])
 
                     stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
                                     time_msec());
-                    if (ovnsb_idl_txn) {
-                        if (ofctrl_has_backlog()) {
-                            /* When there are in-flight messages pending to
-                             * ovs-vswitchd, we should hold on recomputing so
-                             * that the previous flow installations won't be
-                             * delayed.  However, we still want to try if
-                             * recompute is not needed and we can quickly
-                             * incrementally process the new changes, to avoid
-                             * unnecessarily forced recomputes later on.  This
-                             * is because the OVSDB change tracker cannot
-                             * preserve tracked changes across iterations.  If
-                             * change tracking is improved, we can simply skip
-                             * this round of engine_run and continue processing
-                             * acculated changes incrementally later when
-                             * ofctrl_has_backlog() returns false. */
-                            engine_run(false);
-                        } else {
-                            engine_run(true);
-                        }
-                    } else {
-                        /* Even if there's no SB DB transaction available,
+
+                    bool allow_engine_recompute = true;
+
+                    if (!ovnsb_idl_txn || !ovs_idl_txn ||
+                                                     ofctrl_has_backlog()) {
+                        /* When there are in-flight messages pending to
+                         * ovs-vswitchd, we should hold on recomputing so
+                         * that the previous flow installations won't be
+                         * delayed.  However, we still want to try if
+                         * recompute is not needed and we can quickly
+                         * incrementally process the new changes, to avoid
+                         * unnecessarily forced recomputes later on.  This
+                         * is because the OVSDB change tracker cannot
+                         * preserve tracked changes across iterations.  If
+                         * change tracking is improved, we can simply skip
+                         * this round of engine_run and continue processing
+                         * acculated changes incrementally later when
+                         * ofctrl_has_backlog() returns false. */
+
+                        /* Even if there's no SB/OVS DB transaction available,
                          * try to run the engine so that we can handle any
                          * incremental changes that don't require a recompute.
                          * If a recompute is required, the engine will abort,
                          * triggerring a full run in the next iteration.
                          */
-                        engine_run(false);
+                        allow_engine_recompute = false;
                     }
+
+                    engine_run(allow_engine_recompute);
+
                     stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
                                    time_msec());
                     if (engine_has_updated()) {
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 7fe83db64..608220b1f 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -80,6 +80,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("NB_acl", node));
     input_data.nbrec_static_mac_binding_table =
         EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
+    input_data.nbrec_mirror_table =
+        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
 
     input_data.sbrec_sb_global_table =
         EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
@@ -113,6 +115,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
     input_data.sbrec_static_mac_binding_table =
         EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
+    input_data.sbrec_mirror_table =
+        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
 
     northd_run(&input_data, data,
                eng_ctx->ovnnb_idl_txn,
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 54e0ad3b0..ac27a730e 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -50,6 +50,7 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     NB_NODE(acl, "acl") \
     NB_NODE(logical_router, "logical_router") \
     NB_NODE(qos, "qos") \
+    NB_NODE(mirror, "mirror") \
     NB_NODE(meter, "meter") \
     NB_NODE(meter_band, "meter_band") \
     NB_NODE(logical_router_port, "logical_router_port") \
@@ -92,6 +93,7 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     SB_NODE(logical_flow, "logical_flow") \
     SB_NODE(logical_dp_group, "logical_DP_group") \
     SB_NODE(multicast_group, "multicast_group") \
+    SB_NODE(mirror, "mirror") \
     SB_NODE(meter, "meter") \
     SB_NODE(meter_band, "meter_band") \
     SB_NODE(datapath_binding, "datapath_binding") \
@@ -172,6 +174,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_nb_acl, NULL);
     engine_add_input(&en_northd, &en_nb_logical_router, NULL);
     engine_add_input(&en_northd, &en_nb_qos, NULL);
+    engine_add_input(&en_northd, &en_nb_mirror, NULL);
     engine_add_input(&en_northd, &en_nb_meter, NULL);
     engine_add_input(&en_northd, &en_nb_meter_band, NULL);
     engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
@@ -194,6 +197,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_sb_address_set, NULL);
     engine_add_input(&en_northd, &en_sb_port_group, NULL);
     engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
+    engine_add_input(&en_northd, &en_sb_mirror, NULL);
     engine_add_input(&en_northd, &en_sb_meter, NULL);
     engine_add_input(&en_northd, &en_sb_meter_band, NULL);
     engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
diff --git a/northd/northd.c b/northd/northd.c
index b7388afc5..52abdda28 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3248,6 +3248,89 @@  ovn_port_update_sbrec_chassis(
     free(requested_chassis_sb);
 }
 
+static void
+do_sb_mirror_addition(struct northd_input *input_data,
+                      const struct ovn_port *op)
+{
+    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
+        const struct sbrec_mirror *sb_mirror;
+        SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
+                                     input_data->sbrec_mirror_table) {
+            if (!strcmp(sb_mirror->name,
+                        op->nbsp->mirror_rules[i]->name)) {
+                /* Add the value to SB */
+                sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
+                                                                sb_mirror);
+            }
+        }
+    }
+}
+
+static void
+sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
+                                       const struct ovn_port *op)
+{
+    size_t i = 0;
+    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
+        /* Needs deletion in SB */
+        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
+        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
+            shash_add(&nb_mirror_rules,
+                                 op->nbsp->mirror_rules[i]->name,
+                                 op->nbsp->mirror_rules[i]);
+        }
+
+        for (i = 0; i < op->sb->n_mirror_rules; i++) {
+            if (!shash_find(&nb_mirror_rules,
+                           op->sb->mirror_rules[i]->name)) {
+                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
+                                                 op->sb->mirror_rules[i]);
+            }
+        }
+
+        struct shash_node *node, *next;
+        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
+            shash_delete(&nb_mirror_rules, node);
+        }
+        shash_destroy(&nb_mirror_rules);
+
+    } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {
+        /* Needs addition in SB */
+        do_sb_mirror_addition(input_data, op);
+    } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) {
+        /*
+         * Check if its the same mirrors on both SB and NB DBs
+         * If not update accordingly.
+         */
+        bool needs_sb_addition = false;
+        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
+        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
+            shash_add(&nb_mirror_rules,
+                                 op->nbsp->mirror_rules[i]->name,
+                                 op->nbsp->mirror_rules[i]);
+        }
+
+        for (i = 0; i < op->sb->n_mirror_rules; i++) {
+            if (!shash_find(&nb_mirror_rules,
+                           op->sb->mirror_rules[i]->name)) {
+                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
+                                                 op->sb->mirror_rules[i]);
+                    needs_sb_addition = true;
+            }
+        }
+
+        struct shash_node *node, *next;
+        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
+            shash_delete(&nb_mirror_rules, node);
+        }
+        shash_destroy(&nb_mirror_rules);
+
+        if (needs_sb_addition) {
+            do_sb_mirror_addition(input_data, op);
+        }
+    }
+}
+
 static void
 ovn_port_update_sbrec(struct northd_input *input_data,
                       struct ovsdb_idl_txn *ovnsb_txn,
@@ -3607,6 +3690,15 @@  ovn_port_update_sbrec(struct northd_input *input_data,
         }
         sbrec_port_binding_set_external_ids(op->sb, &ids);
         smap_destroy(&ids);
+
+        if (!op->nbsp->n_mirror_rules) {
+            /* Nothing is set. Clear mirror_rules from pb. */
+            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
+        } else {
+            /* Check if SB DB update needed */
+            sbrec_port_binding_update_mirror_rules(input_data, op);
+        }
+
     }
     if (op->tunnel_key != op->sb->tunnel_key) {
         sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
@@ -15014,6 +15106,85 @@  sync_meters(struct northd_input *input_data,
     shash_destroy(&sb_meters);
 }
 
+static bool
+mirror_needs_update(const struct nbrec_mirror *nb_mirror,
+                  const struct sbrec_mirror *sb_mirror)
+{
+
+    if (nb_mirror->index != sb_mirror->index) {
+        return true;
+    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
+        return true;
+    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
+        return true;
+    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
+        return true;
+    }
+
+    return false;
+}
+
+static void
+sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
+                             const char *mirror_name,
+                             const struct nbrec_mirror *nb_mirror,
+                             struct shash *sb_mirrors,
+                             struct sset *used_sb_mirrors)
+{
+    const struct sbrec_mirror *sb_mirror;
+    bool new_sb_mirror = false;
+
+    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
+    if (!sb_mirror) {
+        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
+        sbrec_mirror_set_name(sb_mirror, mirror_name);
+        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
+        new_sb_mirror = true;
+    }
+    sset_add(used_sb_mirrors, mirror_name);
+
+    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
+        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
+        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
+        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
+        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
+    }
+}
+
+static void
+sync_mirrors(struct northd_input *input_data,
+            struct ovsdb_idl_txn *ovnsb_txn)
+{
+    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
+    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
+
+    const struct sbrec_mirror *sb_mirror;
+    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
+        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
+    }
+
+    const struct nbrec_mirror *nb_mirror;
+    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
+        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
+                                     &sb_mirrors, &used_sb_mirrors);
+    }
+
+    const char *used_mirror;
+    const char *used_mirror_next;
+    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
+        shash_find_and_delete(&sb_mirrors, used_mirror);
+        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
+    }
+    sset_destroy(&used_sb_mirrors);
+
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
+        sbrec_mirror_delete(node->data);
+        shash_delete(&sb_mirrors, node);
+    }
+    shash_destroy(&sb_mirrors);
+}
+
 /*
  * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
  * and Southbound db.
@@ -15644,6 +15815,7 @@  ovnnb_db_run(struct northd_input *input_data,
     sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
     sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
     sync_meters(input_data, ovnsb_txn, &data->meter_groups);
+    sync_mirrors(input_data, ovnsb_txn);
     sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
     cleanup_stale_fdb_entries(input_data, &data->datapaths);
     stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
diff --git a/northd/northd.h b/northd/northd.h
index da90e2815..17a62ea10 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -36,6 +36,7 @@  struct northd_input {
     const struct nbrec_acl_table *nbrec_acl_table;
     const struct nbrec_static_mac_binding_table
         *nbrec_static_mac_binding_table;
+    const struct nbrec_mirror_table *nbrec_mirror_table;
 
     /* Southbound table references */
     const struct sbrec_sb_global_table *sbrec_sb_global_table;
@@ -55,6 +56,7 @@  struct northd_input {
     const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
     const struct sbrec_static_mac_binding_table
         *sbrec_static_mac_binding_table;
+    const struct sbrec_mirror_table *sbrec_mirror_table;
 
     /* Indexes */
     struct ovsdb_idl_index *sbrec_chassis_by_name;
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 174364c8b..01de55222 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "6.3.0",
-    "cksum": "4042813038 31869",
+    "version": "6.4.0",
+    "cksum": "589874483 33352",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -132,6 +132,11 @@ 
                                             "refType": "weak"},
                                  "min": 0,
                                  "max": 1}},
+                "mirror_rules": {"type": {"key": {"type": "uuid",
+                                          "refTable": "Mirror",
+                                          "refType": "weak"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "ha_chassis_group": {
                     "type": {"key": {"type": "uuid",
                                      "refTable": "HA_Chassis_Group",
@@ -301,6 +306,28 @@ 
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "isRoot": false},
+        "Mirror": {
+            "columns": {
+                "name": {"type": "string"},
+                "filter": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["from-lport",
+                                                             "to-lport",
+                                                             "both"]]}}},
+                "sink":{"type": "string"},
+                "type": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["gre",
+                                                             "erspan"]]}}},
+                "index": {"type": "integer"},
+                "src": {"type": {"key": {"type": "uuid",
+                                           "refTable": "Logical_Switch_Port",
+                                           "refType": "weak"},
+                                   "min": 0,
+                                   "max": "unlimited"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
         "Meter": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index f41e9d7c0..d8730c8fc 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1554,6 +1554,11 @@ 
       </column>
     </group>
 
+    <column name="mirror_rules">
+        Mirror rules that apply to logical switch port which is the source.
+        Please see the <ref table="Mirror"/> table.
+    </column>
+
     <column name="ha_chassis_group">
       References a row in the OVN Northbound database's
       <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
@@ -2491,6 +2496,64 @@ 
     </column>
   </table>
 
+  <table name="Mirror" title="Mirror Entry">
+    <p>
+      Each row in this table represents one Mirror that can be used for
+      port mirroring. These Mirrors are referenced by the
+      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
+      the <ref table="Logical_Switch_Port"/> table.
+    </p>
+
+    <column name="name">
+      <p>
+        Represents the name of the mirror.
+      </p>
+    </column>
+
+    <column name="filter">
+      <p>
+        The value of this field represents selection criteria of the mirror.
+        Supported values for filter to-lport / from-lport / both
+        to-lport - to mirror packets coming into logical port
+        from-lport - to mirror packets going out of logical port
+        both - to mirror packets coming into and going out of logical port.
+      </p>
+    </column>
+
+    <column name="sink">
+      <p>
+        The value of this field represents the destination/sink of the mirror.
+        The value it takes is an IP address of the sink port.
+      </p>
+    </column>
+
+    <column name="type">
+      <p>
+        The value of this field represents the type of the tunnel used for
+        sending the mirrored packets. Supported Tunnel types gre and erspan
+      </p>
+    </column>
+
+    <column name="index">
+      <p>
+        The value of this field represents the tunnel ID. Depending on the
+        tunnel type configured, GRE key value if type GRE and erspan_idx value
+        if ERSPAN
+      </p>
+    </column>
+
+    <column name="src">
+      <p>
+        The value of this field represents a list of source ports for the
+        mirror. Please see the <ref table="Logical_Switch_Port"/> table.
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
   <table name="Meter" title="Meter entry">
     <p>
       Each row in this table represents a meter that can be used for QoS or
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 576ebbdeb..b83134416 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.25.0",
-    "cksum": "53184112 28845",
+    "version": "20.26.0",
+    "cksum": "2344151793 30004",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -142,6 +142,23 @@ 
             "indexes": [["datapath", "tunnel_key"],
                         ["datapath", "name"]],
             "isRoot": true},
+        "Mirror": {
+            "columns": {
+                "name": {"type": "string"},
+                "filter": {"type": {"key": {"type": "string",
+                                            "enum": ["set",
+                                                     ["from-lport",
+                                                      "to-lport","both"]]}}},
+                "sink":{"type": "string"},
+                "type": {"type": {"key": {"type": "string",
+                                            "enum": ["set",
+                                                     ["gre", "erspan"]]}}},
+                "index": {"type": "integer"},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
         "Meter": {
             "columns": {
                 "name": {"type": "string"},
@@ -230,6 +247,11 @@ 
                                                       "refTable": "Encap",
                                                       "refType": "weak"},
                                     "min": 0, "max": "unlimited"}},
+                "mirror_rules": {"type": {"key": {"type": "uuid",
+                                          "refTable": "Mirror",
+                                          "refType": "weak"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "mac": {"type": {"key": "string",
                                  "min": 0,
                                  "max": "unlimited"}},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 315d60853..05c0db6b4 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2742,6 +2742,51 @@  tcp.flags = RST;
     </column>
   </table>
 
+  <table name="Mirror" title="Mirror Entry">
+    <p>
+      Each row in this table represents one Mirror that can be used for
+      port mirroring. These Mirrors are referenced by the
+      <ref column="mirror_rules" table="Port_Binding"/> column in
+      the <ref table="Port_Binding"/> table.
+    </p>
+
+    <column name="name">
+      <p>
+        Represents the name of the mirror.
+      </p>
+    </column>
+
+    <column name="filter">
+      <p>
+        The value of this field represents selection criteria of the mirror.
+      </p>
+    </column>
+
+    <column name="sink">
+      <p>
+        The value of this field represents the destination/sink of the mirror.
+      </p>
+    </column>
+
+    <column name="type">
+      <p>
+        The value of this field represents the type of the tunnel used for
+        sending the mirrored packets
+      </p>
+    </column>
+
+    <column name="index">
+      <p>
+        The value of this field represents the key/idx depending on the
+        tunnel type configured
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
   <table name="Meter" title="Meter entry">
     <p>
       Each row in this table represents a meter that can be used for QoS or
@@ -3244,6 +3289,11 @@  tcp.flags = RST;
       </column>
     </group>
 
+    <column name="mirror_rules">
+        Mirror rules that apply to the port binding.
+        Please see the <ref table="Mirror"/> table.
+    </column>
+
     <group title="Patch Options">
       <p>
         These options apply to logical ports with <ref column="type"/> of
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 4d480e357..d79f9d929 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -435,6 +435,126 @@  AT_CHECK([ovn-nbctl meter-list], [0], [dnl
 
 dnl ---------------------------------------------------------------------
 
+OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
+AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
+AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
+AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
+AT_CHECK([ovn-nbctl ls-add sw0])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
+
+dnl Add duplicate mirror name
+AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
+AT_CHECK([grep 'already exists' stderr], [0], [ignore])
+
+dnl Attach invalid source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
+AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
+
+dnl Attach source port to invalid mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror4], [1], [], [stderr])
+AT_CHECK([grep 'mirror name not found' stderr], [0], [ignore])
+
+dnl Attach source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
+
+dnl Attach one more source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
+
+dnl Verify if multiple ports are attached to the same mirror properly
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  10.10.10.1
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+mirror2:
+  Type     :  erspan
+  Sink     :  10.10.10.2
+  Filter   :  both
+  Index/Key:  1
+  Sources  :  None attached
+mirror3:
+  Type     :  gre
+  Sink     :  10.10.10.3
+  Filter   :  to-lport
+  Index/Key:  2
+  Sources  :  sw0-port1  sw0-port3
+])
+
+dnl Detach one source port from mirror
+AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
+
+dnl Verify if detach source port from mirror happens properly
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  10.10.10.1
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+mirror2:
+  Type     :  erspan
+  Sink     :  10.10.10.2
+  Filter   :  both
+  Index/Key:  1
+  Sources  :  None attached
+mirror3:
+  Type     :  gre
+  Sink     :  10.10.10.3
+  Filter   :  to-lport
+  Index/Key:  2
+  Sources  :  sw0-port1
+])
+
+dnl Delete a single mirror which has source attached.
+AT_CHECK([ovn-nbctl mirror-del mirror3])
+
+dnl Check if the detach happened from source properly
+AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules |  cut -b 3], [0], [dnl
+
+])
+
+dnl Check if the mirror deleted properly
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  10.10.10.1
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+mirror2:
+  Type     :  erspan
+  Sink     :  10.10.10.2
+  Filter   :  both
+  Index/Key:  1
+  Sources  :  None attached
+])
+
+dnl Delete another mirror
+AT_CHECK([ovn-nbctl mirror-del mirror2])
+
+dnl Update the Sink address
+AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
+
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  192.168.1.13
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+])
+
+dnl Delete all mirrors
+AT_CHECK([ovn-nbctl mirror-del])
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+])])
+
+dnl ---------------------------------------------------------------------
+
 OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
 AT_CHECK([ovn-nbctl lr-add lr0])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 4f399eccb..4e6c268e4 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2319,6 +2319,108 @@  check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([Check NB-SB mirrors sync])
+AT_KEYWORDS([mirrors])
+ovn_start
+
+check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"10.10.10.2"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+erspan
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+0
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+both
+])
+
+check ovn-nbctl --wait=sb \
+    -- set mirror . sink=192.168.1.13
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"192.168.1.13"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+erspan
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+0
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+both
+])
+
+check ovn-nbctl --wait=sb \
+    -- set mirror . type=gre
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+gre
+])
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"192.168.1.13"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+0
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+both
+])
+
+check ovn-nbctl --wait=sb \
+    -- set mirror . index=12
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+12
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+gre
+])
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"192.168.1.13"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+both
+])
+
+check ovn-nbctl --wait=sb \
+    -- set mirror . filter=to-lport
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+to-lport
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+12
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+gre
+])
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"192.168.1.13"
+])
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD_NO_HV([
 AT_SETUP([ACL skip hints for stateless config])
 AT_KEYWORDS([acl])
diff --git a/tests/ovn.at b/tests/ovn.at
index f8b8db4df..cd5527ea1 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16121,6 +16121,784 @@  OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror])
+AT_KEYWORDS([Mirror])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+ovn-nbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+for i in 1 2; do
+    : > vif$i.expected
+done
+
+net_add n2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
+ovn_attach n2 br-phys 192.168.1.12
+
+OVN_POPULATE_ARP
+
+as hv1
+
+# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
+#
+# Causes a packet to be received on INPORT.  The packet is an ICMPv4
+# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
+# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
+# provided, then it should be the ip and icmp checksums of the packet
+# responded; otherwise, no reply is expected.
+# In the absence of an ip checksum calculation helpers, this relies
+# on the caller to provide the checksums for the ip and icmp headers.
+# XXX This should be more systematic.
+#
+# INPORT is an lport number, e.g. 11 for vif11.
+# ETH_SRC and ETH_DST are each 12 hex digits.
+# IPV4_SRC and IPV4_DST are each 8 hex digits.
+# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
+# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
+# ENCAP_TYPE - gre or erspan
+# FILTER - Mirror Filter - to-lport / from-lport
+test_ipv4_icmp_request() {
+    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
+    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
+    shift; shift; shift; shift; shift; shift; shift
+    shift; shift; shift; shift;
+
+    # Use ttl to exercise section 4.2.2.9 of RFC1812
+    local ip_ttl=02
+    local icmp_id=5fbf
+    local icmp_seq=0001
+    local icmp_data=$(seq 1 56 | xargs printf "%02x")
+    local icmp_type_code_request=0800
+    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
+    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
+
+    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
+
+    # Expect to receive the reply, if any. In same port where packet was sent.
+    # Note: src and dst fields are expected to be reversed.
+    local icmp_type_code_response=0000
+    local reply_icmp_ttl=fe
+    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
+    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
+    echo $reply >> vif$inport.expected
+    local remote_mac=000000020200
+    local local_mac=000000010200
+    if test ${mirror_encap_type} = "gre" ; then
+        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
+        if test ${mirror_filter} = "to-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
+        elif test ${mirror_filter} = "from-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
+        fi
+    elif test ${mirror_encap_type} = "erspan" ; then
+        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
+        local erspan_seq0=100088be000000001000000000000000
+        local erspan_seq1=100088be000000011000000000000000
+        if test ${mirror_filter} = "to-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
+        elif test ${mirror_filter} = "from-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
+        fi
+    fi
+    echo $mirror >> br-phys_n1.expected
+
+}
+
+# Set IPs
+rtr_l2_ip=$(ip_to_hex 172 16 1 1)
+l1_ip=$(ip_to_hex 192 168 1 2)
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+
+# Send ping packet and check for mirrored packet of the reply
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . type=erspan
+
+# Send ping packet and check for mirrored packet of the reply
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . filter=from-lport
+
+# Send ping packet and check for mirrored packet of the request
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . type=gre
+
+# Send ping packet and check for mirrored packet of the request
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+echo "---------OVN NB Mirror-----"
+ovn-nbctl mirror-list
+
+echo "---------OVS Mirror----"
+ovs-vsctl list Mirror
+
+echo "-----------------------"
+
+echo "Verifying Mirror deletion in OVS"
+# Set vif1 iface-id such that OVN releases port binding
+check ovs-vsctl set interface vif1 external_ids:iface-id=foo
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
+])
+
+# Set vif1 iface-id back to ls1-lp1
+check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
+
+# Delete vif1 so that OVN releases port binding
+check ovs-vsctl del-port br-int vif1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk swap attachments])
+AT_KEYWORDS([Mirror test bulk swap attachments])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+# Equal detaches and attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk attach multiple])
+AT_KEYWORDS([Mirror test bulk attach multiple])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+
+# Attaches multiple mirrors
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new1"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new2"], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk more detach and less attach])
+AT_KEYWORDS([Mirror test bulk more detach and less attach])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl --wait=hv sync
+origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+
+check ovn-nbctl --wait=hv sync
+
+# Detaches more than attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+
+AT_CHECK([test "$origB" = "$new2"], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk attach more than detach])
+AT_KEYWORDS([Mirror test bulk attach more than detach])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
+check ovn-nbctl --wait=hv sync
+
+# Attaches more than detaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk detach multiple])
+AT_KEYWORDS([Mirror test bulk detach multiple])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+# Detaches all
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+OVN_CLEANUP([hv1])
+AT_CLEANUP
+])
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Port Groups])
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 811468dc6..af2e61435 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -271,6 +271,19 @@  QoS commands:\n\
                             remove QoS rules from SWITCH\n\
   qos-list SWITCH           print QoS rules for SWITCH\n\
 \n\
+Mirror commands:\n\
+  mirror-add NAME TYPE INDEX FILTER IP\n\
+                            add a mirror with given name\n\
+                            specify TYPE 'gre' or 'erspan'\n\
+                            specify the tunnel INDEX value\n\
+                                (indicates key if GRE\n\
+                                 erpsan_idx if ERSPAN)\n\
+                            specify FILTER for mirroring selection\n\
+                                'to-lport' / 'from-lport' / 'both'\n\
+                            specify Sink / Destination i.e. Remote IP\n\
+  mirror-del [NAME]         remove mirrors\n\
+  mirror-list               print mirrors\n\
+\n\
 Meter commands:\n\
   [--fair]\n\
   meter-add NAME ACTION RATE UNIT [BURST]\n\
@@ -311,6 +324,8 @@  Logical switch port commands:\n\
                             set dhcpv6 options for PORT\n\
   lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
   lsp-get-ls PORT           get the logical switch which the port belongs to\n\
+  lsp-attach-mirror PORT MIRROR   attach source PORT to MIRROR\n\
+  lsp-detach-mirror PORT MIRROR   detach source PORT from MIRROR\n\
 \n\
 Forwarding group commands:\n\
   [--liveness]\n\
@@ -1685,6 +1700,130 @@  nbctl_pre_lsp_type(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
 }
 
+static void
+nbctl_pre_lsp_mirror(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
+    ovsdb_idl_add_column(ctx->idl,
+                         &nbrec_logical_switch_port_col_mirror_rules);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static int
+mirror_cmp(const void *mirror1_, const void *mirror2_)
+{
+    const struct nbrec_mirror *const *mirror_1 = mirror1_;
+    const struct nbrec_mirror *const *mirror_2 = mirror2_;
+
+    const struct nbrec_mirror *mirror1 = *mirror_1;
+    const struct nbrec_mirror *mirror2 = *mirror_2;
+
+    return strcmp(mirror1->name,mirror2->name);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
+                    bool must_exist,
+                    const struct nbrec_mirror **mirror_p)
+{
+    const struct nbrec_mirror *mirror = NULL;
+    *mirror_p = NULL;
+
+    struct uuid mirror_uuid;
+    bool is_uuid = uuid_from_string(&mirror_uuid, id);
+    if (is_uuid) {
+        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
+    }
+
+    if (!mirror) {
+        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+            if (!strcmp(mirror->name, id)) {
+                break;
+            }
+        }
+    }
+
+    if (!mirror && must_exist) {
+        return xasprintf("%s: mirror %s not found",
+                         id, is_uuid ? "UUID" : "name");
+    }
+
+    *mirror_p = mirror;
+    return NULL;
+}
+
+static void
+nbctl_lsp_attach_mirror(struct ctl_context *ctx)
+{
+    const char *port = ctx->argv[1];
+    const char *mirror_name = ctx->argv[2];
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_mirror *mirror;
+
+    char *error;
+
+    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+
+    /*check if a mirror rule actually exists on that name or not*/
+    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* Check if same mirror rule already exists for the lsp */
+    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
+        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
+            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+            if (!may_exist) {
+                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
+                          ctx->argv[1]);
+                return;
+            }
+            return;
+        }
+    }
+
+    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
+    nbrec_mirror_update_src_addvalue(mirror,lsp);
+
+}
+
+static void
+nbctl_lsp_detach_mirror(struct ctl_context *ctx)
+{
+    const char *port = ctx->argv[1];
+    const char *mirror_name = ctx->argv[2];
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_mirror *mirror;
+
+    char *error;
+
+    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+
+    /*check if a mirror rule actually exists on that name or not*/
+    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
+    nbrec_mirror_update_src_delvalue(mirror,lsp);
+
+}
+
 static void
 nbctl_lsp_set_type(struct ctl_context *ctx)
 {
@@ -7241,6 +7380,211 @@  cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
     nbrec_ha_chassis_set_priority(ha_chassis, priority);
 }
 
+static char * OVS_WARN_UNUSED_RESULT
+parse_filter(const char *arg, const char **selection_p)
+{
+    /* Validate selection.  Only require the first letter. */
+    if (arg[0] == 't') {
+        *selection_p = "to-lport";
+    } else if (arg[0] == 'f') {
+        *selection_p = "from-lport";
+    } else if (arg[0] == 'b') {
+        *selection_p = "both";
+    } else {
+        *selection_p = NULL;
+        return xasprintf("%s: selection must be \"to-lport\" or "
+                         "\"from-lport\" or \"both\" ", arg);
+    }
+    return NULL;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_type(const char *arg, const char **type_p)
+{
+    /* Validate type.  Only require the first letter. */
+    if (arg[0] == 'g') {
+        *type_p = "gre";
+    } else if (arg[0] == 'e') {
+        *type_p = "erspan";
+    } else {
+        *type_p = NULL;
+        return xasprintf("%s: type must be \"gre\" or "
+                         "\"erspan\"", arg);
+    }
+    return NULL;
+}
+
+static void
+nbctl_pre_mirror_add(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
+}
+
+static void
+nbctl_mirror_add(struct ctl_context *ctx)
+{
+    const char *filter = NULL;
+    const char *sink_ip = NULL;
+    const char *type = NULL;
+    const char *name = NULL;
+    char *new_sink_ip = NULL;
+    int64_t index;
+    char *error = NULL;
+    const struct nbrec_mirror *mirror_check = NULL;
+
+    /* Mirror Name */
+    name = ctx->argv[1];
+    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
+        if (!strcmp(mirror_check->name, name)) {
+            ctl_error(ctx, "Mirror with %s name already exists.",
+                      name);
+            return;
+        }
+    }
+
+    /* Tunnel Type - GRE/ERSPAN */
+    error = parse_type(ctx->argv[2], &type);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* tunnel index / GRE key / ERSPAN idx */
+    index = atoi(ctx->argv[3]);
+
+    /* Filter for mirroring */
+    error = parse_filter(ctx->argv[4], &filter);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* Destination / Sink details */
+    sink_ip = ctx->argv[5];
+
+    /* check if it is a valid ip */
+    new_sink_ip = normalize_ipv4_addr_str(sink_ip);
+    if (!new_sink_ip) {
+        new_sink_ip = normalize_ipv6_addr_str(sink_ip);
+    }
+
+    if (new_sink_ip) {
+        free(new_sink_ip);
+    } else {
+        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
+        return;
+    }
+
+    /* Create the mirror. */
+    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
+    nbrec_mirror_set_name(mirror, name);
+    nbrec_mirror_set_index(mirror, index);
+    nbrec_mirror_set_filter(mirror, filter);
+    nbrec_mirror_set_type(mirror, type);
+    nbrec_mirror_set_sink(mirror, sink_ip);
+
+}
+
+static void
+nbctl_pre_mirror_del(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static void
+nbctl_mirror_del(struct ctl_context *ctx)
+{
+    const struct nbrec_mirror *mirror, *next;
+
+    /* If a name is not specified, delete all mirrors. */
+    if (ctx->argc == 1) {
+        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
+            nbrec_mirror_delete(mirror);
+        }
+        return;
+    }
+
+    /* Remove the matching mirror. */
+    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+        if (strcmp(ctx->argv[1], mirror->name)) {
+            continue;
+        }
+        nbrec_mirror_delete(mirror);
+        return;
+    }
+}
+
+static void
+nbctl_pre_mirror_list(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static void
+nbctl_mirror_list(struct ctl_context *ctx)
+{
+
+    const struct nbrec_mirror **mirrors = NULL;
+    const struct nbrec_mirror *mirror;
+    size_t n_capacity = 0;
+    size_t n_mirrors = 0;
+
+    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+        if (n_mirrors == n_capacity) {
+            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
+        }
+
+        mirrors[n_mirrors] = mirror;
+        n_mirrors++;
+    }
+
+    if (n_mirrors) {
+        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
+    }
+
+    for (size_t i = 0; i < n_mirrors; i++) {
+        mirror = mirrors[i];
+        ds_put_format(&ctx->output, "%s:\n", mirror->name);
+        /* print all the values */
+        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
+        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
+        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
+        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
+                                                (long int) mirror->index);
+        ds_put_cstr(&ctx->output,   "  Sources  :");
+        if (mirror->n_src > 0) {
+            struct svec srcs;
+            const char *src;
+            size_t j;
+            svec_init(&srcs);
+            for (j = 0; j < mirror->n_src; j++) {
+                svec_add(&srcs, mirror->src[j]->name);
+            }
+            svec_sort(&srcs);
+            SVEC_FOR_EACH (j, src, &srcs) {
+                ds_put_format(&ctx->output, "  %s", src);
+            }
+            svec_destroy(&srcs);
+        } else {
+            ds_put_cstr(&ctx->output, "  None attached");
+        }
+        ds_put_cstr(&ctx->output, "\n");
+    }
+
+    free(mirrors);
+}
+
 static const struct ctl_table_class tables[NBREC_N_TABLES] = {
     [NBREC_TABLE_DHCP_OPTIONS].row_ids
     = {{&nbrec_logical_switch_port_col_name, NULL,
@@ -7334,6 +7678,15 @@  static const struct ctl_command_syntax nbctl_commands[] = {
     { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
       NULL, "", RO },
 
+    /* mirror commands. */
+    { "mirror-add", 5, 5,
+      "NAME TYPE INDEX FILTER IP",
+      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
+    { "mirror-del", 0, 1, "[NAME]",
+      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
+    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
+      NULL, "", RO },
+
     /* meter commands. */
     { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
       nbctl_meter_add, NULL, "--fair,--may-exist", RW },
@@ -7388,6 +7741,10 @@  static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
     { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
       NULL, "", RO },
+    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
+      nbctl_lsp_attach_mirror, NULL, "", RW },
+    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
+      nbctl_lsp_detach_mirror, NULL, "", RW },
 
     /* forwarding group commands. */
     { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
index f60dde1b6..3d73e9e25 100644
--- a/utilities/ovn-sbctl.c
+++ b/utilities/ovn-sbctl.c
@@ -307,6 +307,7 @@  pre_get_info(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
 
     ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
     ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
@@ -1431,6 +1432,9 @@  static const struct ctl_table_class tables[SBREC_N_TABLES] = {
     [SBREC_TABLE_HA_CHASSIS_GROUP].row_ids[0]
     = {&sbrec_ha_chassis_group_col_name, NULL, NULL},
 
+    [SBREC_TABLE_MIRROR].row_ids[0]
+    = {&sbrec_mirror_col_name, NULL, NULL},
+
     [SBREC_TABLE_METER].row_ids[0]
     = {&sbrec_meter_col_name, NULL, NULL},