On Wed, Jun 22, 2022 at 1:36 PM Abhiram R N <abhira...@gmail.com> wrote: > > Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller. > While 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> > ---
Hi Abhiram, Thanks for the v3. I did some testing of your patch. I see some issues. I created a mirror - ovn-nbctl mirror-add m1 gre 0 to-lport 192.168.1.12 and also attached it to a logical port - sw0-port1 Issue 1. After that I ran - ovn-nbctl set mirror . sink=192.168.1.13 and I don't see the SB mirror table getting updated with the new mirror value. Issue 2: ovn-controller crashed in my testing once. From the logs I see it crashed while executing ovn_port_mirror_run(). I could not get the core dump though. Here are some logs with json rpc dbg logs enabled - https://paste.openstack.org/show/blc7KJr2eChGLjjpan8x/ Issue 3. Let's say mirror m1 is attached to logical ports sw0-port1 and sw0-port2. When the ovn-controller releases these port bindings due to the ovs ports getting deleted, then the gre interface is not deleted by ovn-controller and the mirror row in the local ovs database is also not deleted. I'd suggest covering all these scenarios in the test cases. > V2 --> V3 : Addressed review comments (as below) > i) Avoided big 'if' and added chassis check > ii) Avoided loop to get interface table > iii) Edited "run" function to do as suggested. > iv) Addressed the ovn-performance.at issue. > > controller/automake.mk | 4 +- > controller/mirror.c | 382 ++++++++++++++++++++++++++++++++++++ > controller/mirror.h | 53 +++++ > controller/ovn-controller.c | 220 ++++++++++++++++++++- > northd/en-northd.c | 4 + > northd/inc-proc-northd.c | 4 + > northd/northd.c | 118 +++++++++++ > northd/northd.h | 2 + > ovn-nb.ovsschema | 31 ++- > ovn-nb.xml | 57 ++++++ > ovn-sb.ovsschema | 23 ++- > ovn-sb.xml | 46 +++++ > tests/ovn-nbctl.at | 80 ++++++++ > tests/ovn.at | 155 +++++++++++++++ > utilities/ovn-nbctl.c | 345 ++++++++++++++++++++++++++++++++ > utilities/ovn-sbctl.c | 4 + > 16 files changed, 1522 insertions(+), 6 deletions(-) > create mode 100644 controller/mirror.c > create mode 100644 controller/mirror.h > > 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..1c96d490a > --- /dev/null > +++ b/controller/mirror.c > @@ -0,0 +1,382 @@ > +/* 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 void > +mirror_create(const struct sbrec_port_binding *pb, > + struct port_mirror_ctx *pm_ctx) > +{ > + const struct ovsrec_mirror *mirror = NULL; > + > + if (pb->up[0] == false) { I think the crash I mentioned is because of this. You need to first check pb->n_up and only then access pb->up[0] since pb->up can be NULL if pb->n_up is 0. > + return; > + } > + > + if (pb->chassis != pm_ctx->chassis_rec) { > + return; > + } > + > + VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port); > + /* Loop through the mirror rules */ > + for (int i =0; i < pb->n_mirror_rules; i++) { Use size_t instead of int for the variable '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); > + > + if (!strcmp(pb->mirror_rules[i]->type, "gre")) { > + /* Set the GRE key */ > + smap_add(&options, "key", key); > + > + } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) { > + /* Set the ERSPAN index */ > + smap_add(&options, "key", key); > + 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-%s", > + pb->mirror_rules[i]->type, > + 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); > + } > + } > + } > +} > + > +static void > +mirror_delete(const struct sbrec_port_binding *pb, > + struct port_mirror_ctx *pm_ctx, > + struct shash *pb_mirror_map, > + bool delete_all) > +{ > + struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors); > + > + if (delete_all != true) { This can be - if (!delete_all) > + for (size_t i = 0; i < pb->n_mirror_rules ; i++) { > + sset_add(&pb_mirrors, pb->mirror_rules[i]->name); > + } > + } > + > + struct shash_node *mirror_node; > + 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 i*/ > + if ((ovs_mirror->n_select_dst_port > 1) || > + (ovs_mirror->n_select_src_port > 1)) { > + /* 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); > +} > + > +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) { > + bool removed = sbrec_port_binding_is_deleted(pb); sbrec_port_binding_is_deleted() is normally used when you run the loop - SBREC_PORT_BINDIN_TABLE_FOR_EACH_TRACKED() and a port binding was deleted in that run. When you run SBREC_PORT_BINDING_TABLE_FOR_EACH(), it will only loop through the existing port binding rows. So you can pass false when calling ovn_port_mirror_handle_lport() here. > + ovn_port_mirror_handle_lport(pb, removed, pm_ctx); > + } > +} > + > +bool > +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool > removed, > + struct port_mirror_ctx *pm_ctx) > +{ > + > + 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 == false) { > + if (((pb->n_mirror_rules == 0) > + && (shash_is_empty(&port_ovs_mirrors))) || > + (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) { > + /* No mirror update */ > + } else { > + /* Update Mirror */ > + if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) { > + /* create mirror, > + * if mirror already exists only update selection > + */ > + mirror_create(pb, pm_ctx); > + } else { > + /* delete mirror, > + * if mirror has other sources only update selection > + */ > + mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false); > + } > + } > + } else { > + 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 true; > +} > + > +bool > +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx) > +{ This function is called whenever a sbrec mirror is added/deleted/updated. And this function loops through all the mirrors. This is inefficient. You can call SBREC_MIRROR_FOR_EACH_TRACKED() and handle the changes for the tracked sbrec mirrors. > + const struct sbrec_mirror *mirror = NULL; > + struct sset sb_mirrors = SSET_INITIALIZER(&sb_mirrors); > + SBREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->sb_mirror_table) { > + sset_add(&sb_mirrors, mirror->name); > + } > + > + struct shash_node *mirror_node; > + SHASH_FOR_EACH (mirror_node, pm_ctx->ovs_mirrors) { > + struct ovsrec_mirror *ovs_mirror = mirror_node->data; > + if (!sset_find(&sb_mirrors, ovs_mirror->name)) { > + /* 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); > + } > + } > + > + const char *used_node, *used_next; > + SSET_FOR_EACH_SAFE (used_node, used_next, &sb_mirrors) { > + sset_delete(&sb_mirrors, SSET_NODE_FROM_NAME(used_node)); > + } > + sset_destroy(&sb_mirrors); > + > + 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 69615308e..3accd2098 100644 > --- a/controller/ovn-controller.c > +++ b/controller/ovn-controller.c > @@ -76,6 +76,7 @@ > #include "stopwatch.h" > #include "lib/inc-proc-eng.h" > #include "hmapx.h" > +#include "mirror.h" > > VLOG_DEFINE_THIS_MODULE(main); > > @@ -958,6 +959,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) > bfd_register_ovs_idl(ovs_idl); > physical_register_ovs_idl(ovs_idl); > vif_plug_register_ovs_idl(ovs_idl); > + mirror_register_ovs_idl(ovs_idl); > } > > #define SB_NODES \ > @@ -978,6 +980,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 { > @@ -995,7 +998,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, > @@ -2263,6 +2267,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. > @@ -3542,6 +3743,7 @@ main(int argc, char *argv[]) > ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets"); > ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups"); > ENGINE_NODE(northd_options, "northd_options"); > + ENGINE_NODE(port_mirror, "port_mirror"); > > #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR); > SB_NODES > @@ -3693,6 +3895,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, > diff --git a/northd/en-northd.c b/northd/en-northd.c > index 4907a1ff2..74cac1b8e 100644 > --- a/northd/en-northd.c > +++ b/northd/en-northd.c > @@ -78,6 +78,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)); > @@ -111,6 +113,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 43093cb5a..f77daafb8 100644 > --- a/northd/inc-proc-northd.c > +++ b/northd/inc-proc-northd.c > @@ -48,6 +48,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") \ > @@ -90,6 +91,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") \ > @@ -168,6 +170,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); > @@ -190,6 +193,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 6997c280c..ddee95e79 100644 > --- a/northd/northd.c > +++ b/northd/northd.c > @@ -3342,6 +3342,51 @@ ovn_port_update_sbrec_chassis( > free(requested_chassis_sb); > } > > +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 { > + /* Needs addition in SB */ > + for (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 > ovn_port_update_sbrec(struct northd_input *input_data, > struct ovsdb_idl_txn *ovnsb_txn, > @@ -3703,6 +3748,17 @@ 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 */ > + if (op->nbsp->n_mirror_rules != op->sb->n_mirror_rules) { > + 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); > @@ -14849,6 +14905,67 @@ sync_meters(struct northd_input *input_data, > shash_destroy(&sb_meters); > } > > +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) { > + 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); > +} > + As I mentioned above in issue 1, ovn-northd is not syncing the mirrors from Northbound to Southbound properly. > /* > * struct 'dns_info' is used to sync the DNS records between OVN Northbound > db > * and Southbound db. > @@ -15452,6 +15569,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 677b35877..56d8a8920 100644 > --- a/northd/northd.h > +++ b/northd/northd.h > @@ -30,6 +30,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; > @@ -49,6 +50,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 9c09de8d8..92a185e19 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -1551,6 +1551,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. > @@ -2481,6 +2486,58 @@ > </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. > + </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="src"> > + <p> > + The value of this field represents the source port 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 3b78ea6f6..7cad0d3c0 100644 > --- a/ovn-sb.ovsschema > +++ b/ovn-sb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_Southbound", > - "version": "20.23.0", > - "cksum": "4045988377 28575", > + "version": "20.24.0", > + "cksum": "618251466 29574", > "tables": { > "SB_Global": { > "columns": { > @@ -142,6 +142,20 @@ > "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"}}, > + "indexes": [["name"]], > + "isRoot": true}, > "Meter": { > "columns": { > "name": {"type": "string"}, > @@ -230,6 +244,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 59ad3aa2d..28285b8c7 100644 > --- a/ovn-sb.xml > +++ b/ovn-sb.xml > @@ -2691,6 +2691,47 @@ 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> > + </table> > + > <table name="Meter" title="Meter entry"> > <p> > Each row in this table represents a meter that can be used for QoS or > @@ -3193,6 +3234,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 726efa6f4..33754e7f5 100644 > --- a/tests/ovn-nbctl.at > +++ b/tests/ovn-nbctl.at > @@ -435,6 +435,86 @@ 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 Add mirror with invalid sink port > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport sw0-port4], [1], [], > [stderr]) > +AT_CHECK([grep 'already exists' 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 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 Detach one source port from mirror > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3]) > + > +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 Detach another source port from mirror > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port1 mirror3]) > + > +dnl Delete a single mirror. > +AT_CHECK([ovn-nbctl mirror-del mirror3]) > + > +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 all mirrors and remove switch > +AT_CHECK([ovn-nbctl mirror-del]) > +AT_CHECK([ovn-nbctl ls-del sw0]) > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl > +])]) Please make sure that you also check the Southbound mirror contents to make sure that is in proper sync. > + > +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.at b/tests/ovn.at > index bfaa41962..5bfa427f3 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -16165,6 +16165,161 @@ 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" > + > +# 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 > +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 > + > +# 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 > +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 > + > +ovs-appctl -t ovn-controller exit I don't understand this test case. Why is ovn-controller stopped ? If you stop, ovn-controller would create the ovs port right ? Thanks Numan > + > +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 MIRROR > +# > +# 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 - Currently checking only for GRE > +# MIRROR - Mirror name > +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_name=${11} > + shift; shift; shift; shift; shift; shift; shift > + shift; shift; shift; shift; > + > + check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0 > to-lport 192.168.1.12 > + check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name} > + > + # 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 > + if test X$exp_icmp_chksum != X; then > + # 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 > + #echo "GRE" > + local > mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply} > + fi > + echo $mirror >> br-phys_n1.expected > + fi > +} > + > +# Set IPs > +rtr_l2_ip=$(ip_to_hex 172 16 1 1) > +l1_ip=$(ip_to_hex 192 168 1 2) > + > +# 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" mirror0 > + > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected]) > + > +tcpdump -r hv1/br-phys_n1-tx.pcap > + > +echo "---------OVN NB Mirror-----" > +ovn-nbctl mirror-list > + > +echo "---------OVS Mirror----" > +ovs-vsctl list Mirror > + > +# Start ovn-controller again just so OVN_CLEANUP doesn't complain > +as hv2 start_daemon ovn-controller > + > +OVN_CLEANUP([hv1], [hv2]) > +AT_CLEANUP > +]) > > OVN_FOR_EACH_NORTHD([ > AT_SETUP([Port Groups]) > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c > index 0d5647b2f..8bd2f7f85 100644 > --- a/utilities/ovn-nbctl.c > +++ b/utilities/ovn-nbctl.c > @@ -271,6 +271,16 @@ 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 INDEX gre key / erpsan idx\n\ > + specify FILTER for mirroring selection\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 +321,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 the MIRROR\n\ > + lsp-detach-mirror PORT MIRROR detach source PORT from the MIRROR\n\ > \n\ > Forwarding group commands:\n\ > [--liveness]\n\ > @@ -1685,6 +1697,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) > { > @@ -7239,6 +7375,202 @@ 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 second 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_external_ip = NULL; > + int64_t index; > + char *error = NULL; > + const struct nbrec_mirror *mirror_check = NULL; > + > + /* Mirror Name */ > + name = ctx->argv[1]; > + NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) { > + if (!strcmp(mirror_check->name, name)) { > + ctl_error(ctx, "Mirror with %s name already exists.", > + name); > + return; > + } > + } > + > + /* Tunnel Type - GRE/ERSPAN */ > + error = parse_type(ctx->argv[2], &type); > + if (error) { > + ctx->error = error; > + return; > + } > + > + /* tunnel index / GRE key / ERSPAN idx */ > + index = atoi(ctx->argv[3]); > + > + /* Filter for mirroring */ > + error = parse_filter(ctx->argv[4], &filter); > + if (error) { > + ctx->error = error; > + return; > + } > + > + /* Destination / Sink details */ > + sink_ip = ctx->argv[5]; > + > + /* check if it is a valid ip */ > + new_external_ip = normalize_ipv4_addr_str(sink_ip); > + if (!new_external_ip) { > + new_external_ip = normalize_ipv6_addr_str(sink_ip); > + } > + > + if (new_external_ip) { > + free(new_external_ip); > + } else { > + ctl_error(ctx, "Invalid sink ip: %s", sink_ip); > + return; > + } > + > + /* Create the mirror. */ > + struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn); > + nbrec_mirror_set_name(mirror, name); > + nbrec_mirror_set_index(mirror, index); > + nbrec_mirror_set_filter(mirror, filter); > + nbrec_mirror_set_type(mirror, type); > + nbrec_mirror_set_sink(mirror, sink_ip); > + > +} > + > +static void > +nbctl_pre_mirror_del(struct ctl_context *ctx) > +{ > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src); > +} > + > +static void > +nbctl_mirror_del(struct ctl_context *ctx) > +{ > + const struct nbrec_mirror *mirror, *next; > + > + /* If a name is not specified, delete all mirrors. */ > + if (ctx->argc == 1) { > + NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) { > + nbrec_mirror_delete(mirror); > + } > + return; > + } > + > + /* Remove the matching mirror. */ > + NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) { > + if (strcmp(ctx->argv[1], mirror->name)) { > + continue; > + } > + nbrec_mirror_delete(mirror); > + return; > + } > +} > + > +static void > +nbctl_pre_mirror_list(struct ctl_context *ctx) > +{ > + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type); > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src); > +} > + > +static void > +nbctl_mirror_list(struct ctl_context *ctx) > +{ > + > + const struct nbrec_mirror **mirrors = NULL; > + const struct nbrec_mirror *mirror; > + size_t n_capacity = 0; > + size_t n_mirrors = 0; > + > + NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) { > + if (n_mirrors == n_capacity) { > + mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors); > + } > + > + mirrors[n_mirrors] = mirror; > + n_mirrors++; > + } > + > + if (n_mirrors) { > + qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp); > + } > + > + for (size_t i = 0; i < n_mirrors; i++) { > + mirror = mirrors[i]; > + ds_put_format(&ctx->output, "%s:\n", mirror->name); > + /* print all the values */ > + ds_put_format(&ctx->output, " Type : %s\n", mirror->type); > + ds_put_format(&ctx->output, " Sink : %s\n", mirror->sink); > + ds_put_format(&ctx->output, " Filter : %s\n", mirror->filter); > + ds_put_format(&ctx->output, " Index/Key: %ld\n", > + (long int)mirror->index); > + ds_put_cstr(&ctx->output, " Sources :"); > + if (mirror->n_src > 0) { > + for (size_t j = 0; j < mirror->n_src; j++) { > + ds_put_format(&ctx->output, " %s", mirror->src[j]->name); > + } > + } 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, > @@ -7332,6 +7664,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 }, > @@ -7386,6 +7727,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 b008b5d0b..14c24dfb9 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); > @@ -1424,6 +1425,9 @@ static const struct ctl_table_class > tables[SBREC_N_TABLES] = { > [SBREC_TABLE_HA_CHASSIS].row_ids[0] > = {&sbrec_ha_chassis_col_chassis, 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.27.0 > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev