diff mbox series

[ovs-dev,v16,3/3] OVN Remote Port Mirroring: controller changes to create ovs mirrors

Message ID 20221125200219.1312140-3-abhiramrn@gmail.com
State Superseded
Headers show
Series [ovs-dev,v16,1/3] OVN Remote Port Mirroring: Add new Schemas in NB and SB | expand

Checks

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

Commit Message

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

Further added test cases in ovn.at to verify end to end
the functioning of Port Mirror and also verify bulk updates
to mirrors.

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

Example commands are as below:

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

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

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

Mirror deletion
ovn-nbctl mirror-del mirror1

Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
---
 NEWS                        |   1 +
 controller/automake.mk      |   4 +-
 controller/mirror.c         | 402 ++++++++++++++++++++++++++++
 controller/mirror.h         |  33 +++
 controller/ovn-controller.c |  54 ++--
 tests/ovn.at                | 514 ++++++++++++++++++++++++++++++++++++
 6 files changed, 985 insertions(+), 23 deletions(-)
 create mode 100644 controller/mirror.c
 create mode 100644 controller/mirror.h
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 6c4573b50..dbffcac0f 100644
--- a/NEWS
+++ b/NEWS
@@ -27,6 +27,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..b947aeb5f
--- /dev/null
+++ b/controller/mirror.c
@@ -0,0 +1,402 @@ 
+/* 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 "include/openvswitch/shash.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);
+
+struct ovn_mirror {
+    char *name;
+    const struct sbrec_mirror *sb_mirror;
+    const struct ovsrec_mirror *ovs_mirror;
+    struct ovs_list mirror_src_lports;
+    struct ovs_list mirror_dst_lports;
+};
+
+struct mirror_lport {
+    struct ovs_list list_node;
+
+    struct local_binding *lbinding;
+};
+
+static struct ovn_mirror *ovn_mirror_create(char *mirror_name);
+static void ovn_mirror_add(struct shash *ovn_mirrors,
+                           struct ovn_mirror *);
+static struct ovn_mirror *ovn_mirror_find(struct shash *ovn_mirrors,
+                                          const char *mirror_name);
+static void ovn_mirror_delete(struct ovn_mirror *);
+static void ovn_mirror_add_lport(struct ovn_mirror *, struct local_binding *);
+static void sync_ovn_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+                            const struct ovsrec_bridge *);
+
+static void create_ovs_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+                              const struct ovsrec_bridge *);
+static void sync_ovs_mirror_ports(struct ovn_mirror *,
+                                  const struct ovsrec_bridge *);
+static void delete_ovs_mirror(struct ovn_mirror *,
+                              const struct ovsrec_bridge *);
+static bool should_delete_ovs_mirror(struct ovn_mirror *);
+
+static const struct ovsrec_port *get_iface_port(
+    const struct ovsrec_interface *, const struct ovsrec_bridge *);
+
+
+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);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_external_ids);
+}
+
+void
+mirror_init(void)
+{
+}
+
+void
+mirror_destroy(void)
+{
+}
+
+void
+mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+           const struct ovsrec_mirror_table *ovs_mirror_table,
+           const struct sbrec_mirror_table *sb_mirror_table,
+           const struct ovsrec_bridge *br_int,
+           struct shash *local_bindings)
+{
+    if (!ovs_idl_txn) {
+        return;
+    }
+
+    struct shash ovn_mirrors = SHASH_INITIALIZER(&ovn_mirrors);
+
+    /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
+    const struct sbrec_mirror *sb_mirror;
+    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
+        struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
+        m->sb_mirror = sb_mirror;
+        ovn_mirror_add(&ovn_mirrors, m);
+    }
+
+    /* Iterate through ovs mirrors and add to the 'ovn_mirrors'. */
+    const struct ovsrec_mirror *ovs_mirror;
+    OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
+        bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
+                                              "ovn-owned", false);
+        if (!ovn_owned_mirror) {
+            continue;
+        }
+
+        struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
+        if (!m) {
+            m = ovn_mirror_create(ovs_mirror->name);
+        }
+        m->ovs_mirror = ovs_mirror;
+    }
+
+    if (shash_is_empty(&ovn_mirrors)) {
+        shash_destroy(&ovn_mirrors);
+        return;
+    }
+
+    /* Iterate through the local bindings and if the local binding's 'pb' has
+     * mirrors associated, add it to the ovn_mirror. */
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, local_bindings) {
+        struct local_binding *lbinding = node->data;
+        const struct sbrec_port_binding *pb =
+            local_binding_get_primary_pb(local_bindings, lbinding->name);
+        if (!pb || !pb->n_mirror_rules) {
+            continue;
+        }
+
+        for (size_t i = 0; i < pb->n_mirror_rules; i++) {
+            struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors,
+                                                   pb->mirror_rules[i]->name);
+            ovs_assert(m);
+            ovn_mirror_add_lport(m, lbinding);
+        }
+    }
+
+    /* Iterate through the built 'ovn_mirrors' and
+     * sync with the local ovsdb i.e.
+     * create/update or delete the ovsrec mirror(s). */
+     SHASH_FOR_EACH (node, &ovn_mirrors) {
+        struct ovn_mirror *m = node->data;
+        sync_ovn_mirror(m, ovs_idl_txn, br_int);
+     }
+
+    SHASH_FOR_EACH_SAFE (node, &ovn_mirrors) {
+        ovn_mirror_delete(node->data);
+        shash_delete(&ovn_mirrors, node);
+    }
+
+    shash_destroy(&ovn_mirrors);
+
+}
+
+/* Static functions. */
+static struct ovn_mirror *
+ovn_mirror_create(char *mirror_name)
+{
+    struct ovn_mirror *m = xzalloc(sizeof *m);
+    m->name = xstrdup(mirror_name);
+    ovs_list_init(&m->mirror_src_lports);
+    ovs_list_init(&m->mirror_dst_lports);
+    return m;
+}
+
+static void
+ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
+{
+    shash_add(ovn_mirrors, m->sb_mirror->name, m);
+}
+
+static struct ovn_mirror *
+ovn_mirror_find(struct shash *ovn_mirrors, const char *mirror_name)
+{
+    return shash_find_data(ovn_mirrors, mirror_name);
+}
+
+static void
+ovn_mirror_delete(struct ovn_mirror *m)
+{
+    free(m->name);
+    struct mirror_lport *m_lport;
+    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_src_lports) {
+        free(m_lport);
+    }
+
+    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_dst_lports) {
+        free(m_lport);
+    }
+
+    free(m);
+}
+
+static void
+ovn_mirror_add_lport(struct ovn_mirror *m, struct local_binding *lbinding)
+{
+    struct mirror_lport *m_lport = xzalloc(sizeof *m_lport);
+    m_lport->lbinding = lbinding;
+    if (!strcmp(m->sb_mirror->filter, "from-lport")) {
+        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+    } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
+        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+    } else {
+        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+        m_lport = xzalloc(sizeof *m_lport);
+        m_lport->lbinding = lbinding;
+        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+    }
+}
+
+static void
+create_and_set_options(struct ovsrec_interface *iface,
+                       const struct sbrec_mirror *sb_mirror)
+{
+    struct smap options = SMAP_INITIALIZER(&options);
+    char *key;
+
+    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);
+
+    free(key);
+    smap_destroy(&options);
+}
+
+static void
+check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+                                 const struct ovsrec_mirror *ovs_mirror)
+{
+    char *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);
+    }
+    create_and_set_options(iface, sb_mirror);
+}
+
+static void
+sync_ovn_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+                const struct ovsrec_bridge *br_int)
+{
+    if (should_delete_ovs_mirror(m)) {
+        /* Delete the ovsrec mirror. */
+        delete_ovs_mirror(m, br_int);
+        return;
+    }
+
+    if (ovs_list_is_empty(&m->mirror_src_lports) &&
+            ovs_list_is_empty(&m->mirror_dst_lports)) {
+        /* Nothing to do. */
+        return;
+    }
+
+    if (m->sb_mirror && !m->ovs_mirror) {
+        create_ovs_mirror(m, ovs_idl_txn, br_int);
+    } else {
+        check_and_update_interface_table(m->sb_mirror, m->ovs_mirror);
+    }
+
+    sync_ovs_mirror_ports(m, br_int);
+}
+
+static bool
+should_delete_ovs_mirror(struct ovn_mirror *m)
+{
+    if (!m->ovs_mirror) {
+        return false;
+    }
+
+    if (m->ovs_mirror && !m->sb_mirror) {
+        return true;
+    }
+
+    return (ovs_list_is_empty(&m->mirror_src_lports) &&
+            ovs_list_is_empty(&m->mirror_dst_lports));
+}
+
+static const struct ovsrec_port *
+get_iface_port(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 void
+create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+                  const struct ovsrec_bridge *br_int)
+{
+    struct ovsrec_interface *iface = ovsrec_interface_insert(ovs_idl_txn);
+    char *port_name = xasprintf("ovn-%s", m->name);
+
+    ovsrec_interface_set_name(iface, port_name);
+    ovsrec_interface_set_type(iface, m->sb_mirror->type);
+    create_and_set_options(iface, m->sb_mirror);
+
+    struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+    ovsrec_bridge_update_ports_addvalue(br_int, port);
+
+    free(port_name);
+
+    m->ovs_mirror = ovsrec_mirror_insert(ovs_idl_txn);
+    ovsrec_mirror_set_name(m->ovs_mirror, m->name);
+    ovsrec_mirror_set_output_port(m->ovs_mirror, port);
+
+    const struct smap external_ids =
+        SMAP_CONST1(&external_ids, "ovn-owned", "true");
+    ovsrec_mirror_set_external_ids(m->ovs_mirror, &external_ids);
+
+    ovsrec_bridge_update_mirrors_addvalue(br_int, m->ovs_mirror);
+}
+
+static void
+sync_ovs_mirror_ports(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+    struct mirror_lport *m_lport;
+
+    if (ovs_list_is_empty(&m->mirror_src_lports)) {
+        ovsrec_mirror_set_select_src_port(m->ovs_mirror, NULL, 0);
+    } else {
+        size_t n_lports = ovs_list_size(&m->mirror_src_lports);
+        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+        size_t i = 0;
+        LIST_FOR_EACH (m_lport, list_node, &m->mirror_src_lports) {
+            const struct ovsrec_port *p =
+                get_iface_port(m_lport->lbinding->iface, br_int);
+            ovs_assert(p);
+            ovs_ports[i++] = (struct ovsrec_port *) p;
+        }
+
+        ovsrec_mirror_set_select_src_port(m->ovs_mirror, ovs_ports, n_lports);
+        free(ovs_ports);
+    }
+
+    if (ovs_list_is_empty(&m->mirror_dst_lports)) {
+        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, NULL, 0);
+    } else {
+        size_t n_lports = ovs_list_size(&m->mirror_dst_lports);
+        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+        size_t i = 0;
+        LIST_FOR_EACH (m_lport, list_node, &m->mirror_dst_lports) {
+            const struct ovsrec_port *p =
+                get_iface_port(m_lport->lbinding->iface, br_int);
+            ovs_assert(p);
+            ovs_ports[i++] = (struct ovsrec_port *) p;
+        }
+
+        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, ovs_ports, n_lports);
+        free(ovs_ports);
+    }
+}
+
+static void
+delete_ovs_mirror(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+    ovsrec_bridge_update_ports_delvalue(br_int, m->ovs_mirror->output_port);
+    ovsrec_bridge_update_mirrors_delvalue(br_int, m->ovs_mirror);
+    ovsrec_port_delete(m->ovs_mirror->output_port);
+    ovsrec_mirror_delete(m->ovs_mirror);
+}
diff --git a/controller/mirror.h b/controller/mirror.h
new file mode 100644
index 000000000..a79de109d
--- /dev/null
+++ b/controller/mirror.h
@@ -0,0 +1,33 @@ 
+/* 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_mirror_table;
+struct sbrec_mirror_table;
+struct ovsrec_bridge;
+struct shash;
+
+void mirror_register_ovs_idl(struct ovsdb_idl *);
+void mirror_init(void);
+void mirror_destroy(void);
+void mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+                const struct ovsrec_mirror_table *,
+                const struct sbrec_mirror_table *,
+                const struct ovsrec_bridge *,
+                struct shash *local_bindings);
+#endif
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 0752a71ad..0e1d31dbd 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);
 
@@ -994,6 +995,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 \
@@ -3619,6 +3621,7 @@  main(int argc, char *argv[])
     patch_init();
     pinctrl_init();
     lflow_init();
+    mirror_init();
     vif_plug_provider_initialize();
 
     /* Connect to OVS OVSDB instance. */
@@ -4195,34 +4198,35 @@  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 || 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()) {
@@ -4312,6 +4316,11 @@  main(int argc, char *argv[])
                                     &runtime_data->local_active_ports_ras);
                         stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
                                        time_msec());
+                        mirror_run(ovs_idl_txn,
+                                   ovsrec_mirror_table_get(ovs_idl_loop.idl),
+                                   sbrec_mirror_table_get(ovnsb_idl_loop.idl),
+                                   br_int,
+                                   &runtime_data->lbinding_data.bindings);
                         /* Updating monitor conditions if runtime data or
                          * logical datapath goups changed. */
                         if (engine_node_changed(&en_runtime_data)
@@ -4555,6 +4564,7 @@  loop_done:
     pinctrl_destroy();
     binding_destroy();
     patch_destroy();
+    mirror_destroy();
     if_status_mgr_destroy(if_mgr);
     shash_destroy(&vif_plug_deleted_iface_ids);
     shash_destroy(&vif_plug_changed_iface_ids);
diff --git a/tests/ovn.at b/tests/ovn.at
index 0ef536509..1769f4906 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16237,6 +16237,520 @@  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 updates])
+AT_KEYWORDS([Mirror test bulk updates])
+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 2 hypervisors and create OVS ports corresponding to logical ports for hv1.
+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
+
+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
+
+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
+ovn-nbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+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 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 --wait=hv sync
+origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+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], [])
+
+# 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
+
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+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], [])
+
+# 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 ls2-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
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+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], [])
+
+# 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
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+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], [])
+
+# Detaches all
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp1 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)])
+
+check ovn-nbctl mirror-add mirror2 gre 2 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check ovn-nbctl --wait=hv sync
+
+# Attaches SAME port to multiple mirrors
+# and detaches it from existing mirror.
+# More attach than detach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])
+
+check ovn-nbctl mirror-add mirror3 gre 3 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror3
+check ovn-nbctl --wait=hv sync
+
+# Detaches SAME port from multiple mirrors
+# and attaches it to existing mirror.
+# More detach than attach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+check ovn-nbctl mirror-add mirror0 erspan 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+
+# Make sure different fields of mirror resource set from OVN
+# propagates to OVS correctly
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl set mirror . filter=from-lport
+check ovn-nbctl set mirror . type=gre
+check ovn-nbctl set mirror . index=123
+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_src_port)
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+type=$(ovs-vsctl get Interface ovn-mirror0 type)
+OVS_WAIT_UNTIL([test "gre" = "$type"])
+index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
+OVS_WAIT_UNTIL([test "1" = "$index"])
+
+OVN_CLEANUP([hv1],[hv2])
+AT_CLEANUP
+])
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Port Groups])