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 <ihrac...@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 <abhira...@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 <vedabarrenk...@gmail.com>
> >>> Signed-off-by: Veda Barrenkala <vedabarrenk...@gmail.com>
> >>> Signed-off-by: Abhiram R N <abhira...@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
> >>>
>
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to