Abhiram, Thanks for confirming the code should already handle bulk updates; perhaps then I just misunderstand how it works. Can we get test cases added for these bulk update scenarios? This should be achievable eg. as in:
check as hv1 ovn-appctl -t ovn-controller debug/pause # modify mirror relationships check as hv1 ovn-appctl -t ovn-controller debug/resume # check that mirrors are properly set in ovsdb There are several test cases in the tree that already emulate bulk / delayed updates using this technique, for your reference. Thanks again, Ihar On Mon, Oct 3, 2022 at 3:13 PM Abhiram R N <a...@redhat.com> wrote: > > Hi Ihar, > > Thanks for your review. > Please see below for replies inline. > > On Mon, Oct 3, 2022 at 11:28 PM Ihar Hrachyshka <ihrac...@redhat.com> wrote: >> >> NEWS file should be updated. See more comments inline below. > > Sure. Will update the NEWS file in the next patch. >> >> >> On Wed, Sep 28, 2022 at 9:08 AM 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> >> > --- >> > V6 --> V7: Addressed review comments from V6 by Numan >> > i) Did suggested changes in mirror.c and ovn-controller.c >> > ii) Handled the use-case and added to ovn.at test >> > iii) Indentation correction in ovn-nbctl.c >> > >> > Files modified (V6 --> v7): >> > Code --> mirror.c, ovn-controller.c, ovn-nbctl.c >> > Test --> ovn.at >> > >> > controller/automake.mk | 4 +- >> > controller/mirror.c | 537 ++++++++++++++++++++++++++++++++++++ >> > controller/mirror.h | 53 ++++ >> > controller/ovn-controller.c | 266 ++++++++++++++++-- >> > northd/en-northd.c | 4 + >> > northd/inc-proc-northd.c | 4 + >> > northd/northd.c | 172 ++++++++++++ >> > northd/northd.h | 2 + >> > ovn-nb.ovsschema | 31 ++- >> > ovn-nb.xml | 63 +++++ >> > ovn-sb.ovsschema | 26 +- >> > ovn-sb.xml | 50 ++++ >> > tests/ovn-nbctl.at | 116 ++++++++ >> > tests/ovn-northd.at | 102 +++++++ >> > tests/ovn.at | 231 ++++++++++++++++ >> > utilities/ovn-nbctl.c | 357 ++++++++++++++++++++++++ >> > utilities/ovn-sbctl.c | 4 + >> > 17 files changed, 1994 insertions(+), 28 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..37f8b2e9f >> > --- /dev/null >> > +++ b/controller/mirror.c >> > @@ -0,0 +1,537 @@ >> > +/* Copyright (c) 2022 Red Hat, Inc. >> > + * >> > + * Licensed under the Apache License, Version 2.0 (the "License"); >> > + * you may not use this file except in compliance with the License. >> > + * You may obtain a copy of the License at: >> > + * >> > + * http://www.apache.org/licenses/LICENSE-2.0 >> > + * >> > + * Unless required by applicable law or agreed to in writing, software >> > + * distributed under the License is distributed on an "AS IS" BASIS, >> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or >> > implied. >> > + * See the License for the specific language governing permissions and >> > + * limitations under the License. >> > + */ >> > + >> > +#include <config.h> >> > +#include <unistd.h> >> > + >> > +/* library headers */ >> > +#include "lib/sset.h" >> > +#include "lib/util.h" >> > + >> > +/* OVS includes. */ >> > +#include "lib/vswitch-idl.h" >> > +#include "openvswitch/vlog.h" >> > + >> > +/* OVN includes. */ >> > +#include "binding.h" >> > +#include "lib/ovn-sb-idl.h" >> > +#include "mirror.h" >> > + >> > +VLOG_DEFINE_THIS_MODULE(port_mirror); >> > + >> > +/* Static function declarations */ >> > + >> > +static const struct ovsrec_port * >> > +get_port_for_iface(const struct ovsrec_interface *iface, >> > + const struct ovsrec_bridge *br_int) >> > +{ >> > + for (size_t i = 0; i < br_int->n_ports; i++) { >> > + const struct ovsrec_port *p = br_int->ports[i]; >> > + for (size_t j = 0; j < p->n_interfaces; j++) { >> > + if (!strcmp(iface->name, p->interfaces[j]->name)) { >> > + return p; >> > + } >> > + } >> > + } >> > + return NULL; >> > +} >> > + >> > +static bool >> > +mirror_create(const struct sbrec_port_binding *pb, >> > + struct port_mirror_ctx *pm_ctx) >> > +{ >> > + const struct ovsrec_mirror *mirror = NULL; >> > + >> > + if (pb->n_up && !pb->up[0]) { >> > + return true; >> > + } >> > + >> > + if (pb->chassis != pm_ctx->chassis_rec) { >> > + return true; >> > + } >> > + >> > + if (!pm_ctx->ovs_idl_txn) { >> > + return false; >> > + } >> > + >> > + >> > + VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port); >> > + /* Loop through the mirror rules */ >> > + for (size_t i =0; i < pb->n_mirror_rules; i++) { >> > + /* check if the mirror already exists in OVS DB */ >> > + bool create_mirror = true; >> > + OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) { >> > + if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) { >> > + /* Mirror with same name already exists >> > + * No need to create mirror >> > + */ >> > + create_mirror = false; >> > + break; >> > + } >> > + } >> > + >> > + if (create_mirror) { >> > + >> > + struct smap options = SMAP_INITIALIZER(&options); >> > + char *port_name, *key; >> > + >> > + key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index); >> > + smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink); >> > + >> > + 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", >> > + pb->mirror_rules[i]->name); >> > + >> > + ovsrec_interface_set_name(iface, port_name); >> > + ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type); >> > + ovsrec_interface_set_options(iface, &options); >> > + >> > + struct ovsrec_port *port = >> > + ovsrec_port_insert(pm_ctx->ovs_idl_txn); >> > + ovsrec_port_set_name(port, port_name); >> > + ovsrec_port_set_interfaces(port, &iface, 1); >> > + >> > + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port); >> > + >> > + smap_destroy(&options); >> > + free(port_name); >> > + free(key); >> > + >> > + VLOG_INFO("Creating Mirror in OVS DB"); >> > + mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn); >> > + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name); >> > + ovsrec_mirror_update_output_port_addvalue(mirror, port); >> > + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int, >> > + mirror); >> > + } >> > + >> > + struct local_binding *lbinding = local_binding_find( >> > + pm_ctx->local_bindings, pb->logical_port); >> > + const struct ovsrec_port *p = >> > + get_port_for_iface(lbinding->iface, pm_ctx->br_int); >> > + if (p) { >> > + if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) { >> > + ovsrec_mirror_update_select_src_port_addvalue(mirror, p); >> > + } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) { >> > + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p); >> > + } else { >> > + ovsrec_mirror_update_select_src_port_addvalue(mirror, p); >> > + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p); >> > + } >> > + } >> > + } >> > + return true; >> > +} >> > + >> > +static void >> > +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror, >> > + struct ovsrec_mirror *ovs_mirror) >> > +{ >> > + char *filter; >> > + if ((ovs_mirror->n_select_dst_port) >> > + && (ovs_mirror->n_select_src_port)) { >> > + filter = "both"; >> > + } else if (ovs_mirror->n_select_dst_port) { >> > + filter = "to-lport"; >> > + } else { >> > + filter = "from-lport"; >> > + } >> > + >> > + if (strcmp(sb_mirror->filter, filter)) { >> > + if (!strcmp(sb_mirror->filter,"from-lport") >> > + && !strcmp(filter,"both")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { >> > + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror, >> > + >> > ovs_mirror->select_dst_port[i]); >> > + } >> > + } else if (!strcmp(sb_mirror->filter,"to-lport") >> > + && !strcmp(filter,"both")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { >> > + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror, >> > + >> > ovs_mirror->select_src_port[i]); >> > + } >> > + } else if (!strcmp(sb_mirror->filter,"both") >> > + && !strcmp(filter,"from-lport")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { >> > + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror, >> > + >> > ovs_mirror->select_src_port[i]); >> > + } >> > + } else if (!strcmp(sb_mirror->filter,"both") >> > + && !strcmp(filter,"to-lport")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { >> > + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror, >> > + >> > ovs_mirror->select_dst_port[i]); >> > + } >> > + } else if (!strcmp(sb_mirror->filter,"to-lport") >> > + && !strcmp(filter,"from-lport")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { >> > + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror, >> > + >> > ovs_mirror->select_src_port[i]); >> > + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror, >> > + >> > ovs_mirror->select_src_port[i]); >> > + } >> > + } else if (!strcmp(sb_mirror->filter,"from-lport") >> > + && !strcmp(filter,"to-lport")) { >> > + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { >> > + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror, >> > + >> > ovs_mirror->select_dst_port[i]); >> > + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror, >> > + >> > ovs_mirror->select_dst_port[i]); >> > + } >> > + } >> > + } >> > +} >> > + >> > +static void >> > +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror, >> > + struct ovsrec_mirror *ovs_mirror) >> > +{ >> > + struct smap options = SMAP_INITIALIZER(&options); >> > + char *key, *type; >> > + struct smap *opts = &ovs_mirror->output_port->interfaces[0]->options; >> > + struct ovsrec_interface *iface = >> > + ovs_mirror->output_port->interfaces[0]; >> > + >> >> nit: first calculate iface, then do: opts = iface->options > > Sure. >> >> >> > + const char *erspan_ver = smap_get(opts, "erspan_ver"); >> > + if (erspan_ver) { >> > + type = "erspan"; >> > + } else { >> > + type = "gre"; >> > + } >> > + if (strcmp(type, sb_mirror->type)) { >> > + ovsrec_interface_set_type(iface, sb_mirror->type); >> > + } >> > + >> > + key = xasprintf("%ld",(long int)sb_mirror->index); >> >> Why do we need to force convert to long int? Wouldn't "%d" formatter >> work the same? > > I guess it should. Will change it and check once though >> >> >> > + smap_add(&options, "remote_ip", sb_mirror->sink); >> > + >> > + if (!strcmp(sb_mirror->type, "gre")) { >> > + /* Set the GRE key */ >> > + smap_add(&options, "key", key); >> >> nit: This is common to all types, so maybe move it outside the if >> block and remove the duplicate line below. That said... > > Sure. >> >> >> > + >> > + } else if (!strcmp(sb_mirror->type, "erspan")) { >> > + /* Set the ERSPAN index */ >> > + smap_add(&options, "key", key); >> >> ... is it really needed to set "key" here? Doesn't "erspan_idx" >> override it for erspan tunnels? (It's more of a question, not a >> statement. I really don't know.) > > Yeah it's needed from what I remember. > Although yeah I agree they are having erspan_idx. > Will recheck it and keep it outside if so as suggested above. >> >> >> > + smap_add(&options, "erspan_idx", key); >> > + smap_add(&options, "erspan_ver","1"); >> > + >> > + } >> >> nit: you could combine the if-else-if block with the 'if (erspan_ver)' >> block above. > > Sure. >> >> >> > + >> > + ovsrec_interface_set_options(iface, &options); >> > + smap_destroy(&options); >> > + free(key); >> > + >> > +} >> > + >> > +static void >> > +mirror_update(const struct sbrec_mirror *sb_mirror, >> > + struct ovsrec_mirror *ovs_mirror) >> > +{ >> > + check_and_update_interface_table(sb_mirror, ovs_mirror); >> > + >> > + check_and_update_mirror_table(sb_mirror, ovs_mirror); >> > +} >> > + >> > +static bool >> > +mirror_delete(const struct sbrec_port_binding *pb, >> > + struct port_mirror_ctx *pm_ctx, >> > + struct shash *pb_mirror_map, >> > + bool delete_all) >> > +{ >> > + >> > + if (!pm_ctx->ovs_idl_txn) { >> > + return false; >> > + } >> > + >> > + struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors); >> > + >> > + if (!delete_all) { >> > + for (size_t i = 0; i < pb->n_mirror_rules ; i++) { >> > + sset_add(&pb_mirrors, pb->mirror_rules[i]->name); >> > + } >> > + } >> > + >> > + if (delete_all && (shash_is_empty(pb_mirror_map)) && >> > pb->n_mirror_rules) { >> > + for (size_t i = 0; i < pb->n_mirror_rules ; i++) { >> > + >> > + struct ovsrec_mirror *ovs_mirror = NULL; >> > + ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, >> > + pb->mirror_rules[i]->name); >> > + if (ovs_mirror) { >> > + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int, >> > + ovs_mirror->output_port); >> > + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int, >> > + ovs_mirror); >> > + ovsrec_port_delete(ovs_mirror->output_port); >> > + ovsrec_mirror_delete(ovs_mirror); >> > + } >> > + } >> > + } >> > + >> > + struct shash_node *mirror_node; >> > + 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); >> > + >> > + return true; >> > +} >> > + >> > +static void >> > +find_port_specific_mirrors (const struct sbrec_port_binding *pb, >> > + struct port_mirror_ctx *pm_ctx, >> > + struct shash *pb_mirror_map) >> > +{ >> > + const struct ovsrec_mirror *mirror = NULL; >> > + >> > + OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) { >> > + for (size_t i = 0; i < mirror->n_select_dst_port; i++) { >> > + const struct ovsrec_port *port_rec = >> > mirror->select_dst_port[i]; >> > + for (size_t j = 0; j < port_rec->n_interfaces; j++) { >> > + const struct ovsrec_interface *iface_rec; >> > + iface_rec = port_rec->interfaces[j]; >> > + const char *logical_port = >> > + smap_get(&iface_rec->external_ids, "iface-id"); >> > + if (!strcmp(logical_port, pb->logical_port)) { >> > + shash_add_once(pb_mirror_map, mirror->name, mirror); >> > + } >> > + } >> > + } >> > + for (size_t i = 0; i < mirror->n_select_src_port; i++) { >> > + const struct ovsrec_port *port_rec = >> > mirror->select_src_port[i]; >> > + for (size_t j = 0; j < port_rec->n_interfaces; j++) { >> > + const struct ovsrec_interface *iface_rec; >> > + iface_rec = port_rec->interfaces[j]; >> > + const char *logical_port = >> > + smap_get(&iface_rec->external_ids, "iface-id"); >> > + if (!strcmp(logical_port, pb->logical_port)) { >> > + shash_add_once(pb_mirror_map, mirror->name, mirror); >> > + } >> > + } >> > + } >> > + } >> > +} >> > + >> > +void >> > +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl) >> > +{ >> > + ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors); >> > + >> > + ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror); >> > + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name); >> > + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port); >> > + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port); >> > + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port); >> > +} >> > + >> > + >> > +void >> > +ovn_port_mirror_init(struct shash *ovs_mirrors) >> > +{ >> > + shash_init(ovs_mirrors); >> > +} >> > + >> > +void >> > +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx) >> > +{ >> > + const struct sbrec_port_binding *pb; >> > + SBREC_PORT_BINDING_TABLE_FOR_EACH (pb, >> > + pm_ctx->port_binding_table) { >> > + ovn_port_mirror_handle_lport(pb, false, pm_ctx); >> > + } >> > +} >> > + >> > +bool >> > +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool >> > removed, >> > + struct port_mirror_ctx *pm_ctx) >> > +{ >> > + bool ret = true; >> > + struct local_binding *lbinding = local_binding_find( >> > + pm_ctx->local_bindings, pb->logical_port); >> > + >> > + if (strcmp(pb->type, "") && (!lbinding)) { >> > + return ret; >> > + } >> > + >> > + struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors); >> > + >> > + /* Need to find if mirror needs update */ >> > + find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors); >> > + if (!removed) { >> > + if ((pb->n_mirror_rules == 0) >> > + && (shash_is_empty(&port_ovs_mirrors))) { >> > + /* No mirror update */ >> > + } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) { >> > + /* Though number of mirror rules are same, >> > + * need to verify the contents >> > + */ >> > + for (size_t i = 0; i < pb->n_mirror_rules; i++) { >> > + if (!shash_find(&port_ovs_mirrors, >> > + pb->mirror_rules[i]->name)) { >> > + /* Mis match in OVN SB DB and OVS DB >> > + * Delete and Create mirror(s) with proper sources >> > + */ >> > + ret = mirror_delete(pb, pm_ctx, >> > + &port_ovs_mirrors, false); >> > + if (ret) { >> > + ret = mirror_create(pb, pm_ctx); >> > + } >> > + break; >> >> This break suggests that if *two* mirror entries changed, then you >> will handle one of the changes here only. This seems incorrect. I >> think we had a similar discussion before around the possibility of >> bulk updates to mirror bindings where multiple mirror relationships >> can be included in the same transaction handled by the engine. The >> engine handler should be ready to handle bulk updates. I believe there >> are some debug commands available in appctl to emulate bulk updates in >> test cases ("pause"). You could use them to test the scenarios in test >> suite. > > Already mirrror_create and mirror_delete takes care of bulk updates. > I thought about it and since we are already looping through the mirror_rules > inside of the above 2 functions it gets handled. > Infact, I have tested the same with 2 mirror updates. :) >> >> >> > + } >> > + } >> > + } else { >> > + /* Update Mirror */ >> > + if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) { >> > + /* create mirror, >> > + * if mirror already exists only update selection >> > + */ >> > + ret = mirror_create(pb, pm_ctx); >> > + } else { >> > + /* delete mirror, >> > + * if mirror has other sources only update selection >> > + */ >> > + ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false); >> > + } >> > + } >> > + } else { >> > + ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true); >> > + } >> > + >> > + struct shash_node *ovs_mirror_node, *ovs_mirror_next; >> > + SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next, >> > + &port_ovs_mirrors) { >> > + shash_delete(&port_ovs_mirrors, ovs_mirror_node); >> > + } >> > + shash_destroy(&port_ovs_mirrors); >> > + >> > + return ret; >> > +} >> > + >> > +bool >> > +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx) >> > +{ >> > + const struct sbrec_mirror *mirror = NULL; >> > + struct ovsrec_mirror *ovs_mirror = NULL; >> > + >> > + SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) >> > { >> > + /* For each tracked mirror entry check if OVS entry is there*/ >> > + ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name); >> > + if (ovs_mirror) { >> > + if (sbrec_mirror_is_deleted(mirror)) { >> > + /* Need to delete the mirror in OVS */ >> > + VLOG_INFO("Delete mirror and remove port"); >> > + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int, >> > + >> > ovs_mirror->output_port); >> > + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int, >> > + ovs_mirror); >> > + ovsrec_port_delete(ovs_mirror->output_port); >> > + ovsrec_mirror_delete(ovs_mirror); >> > + } else { >> > + mirror_update(mirror, ovs_mirror); >> > + } >> > + } >> > + } >> > + >> > + return true; >> > +} >> > + >> > +void >> > +ovn_port_mirror_destroy(struct shash *ovs_mirrors) >> > +{ >> > + struct shash_node *ovs_mirror_node, *ovs_mirror_next; >> > + SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next, >> > + ovs_mirrors) { >> > + shash_delete(ovs_mirrors, ovs_mirror_node); >> > + } >> > + shash_destroy(ovs_mirrors); >> > +} >> > diff --git a/controller/mirror.h b/controller/mirror.h >> > new file mode 100644 >> > index 000000000..85b964f55 >> > --- /dev/null >> > +++ b/controller/mirror.h >> > @@ -0,0 +1,53 @@ >> > +/* Copyright (c) 2022 Red Hat, Inc. >> > + * >> > + * Licensed under the Apache License, Version 2.0 (the "License"); >> > + * you may not use this file except in compliance with the License. >> > + * You may obtain a copy of the License at: >> > + * >> > + * http://www.apache.org/licenses/LICENSE-2.0 >> > + * >> > + * Unless required by applicable law or agreed to in writing, software >> > + * distributed under the License is distributed on an "AS IS" BASIS, >> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or >> > implied. >> > + * See the License for the specific language governing permissions and >> > + * limitations under the License. >> > + */ >> > + >> > +#ifndef OVN_MIRROR_H >> > +#define OVN_MIRROR_H 1 >> > + >> > +struct ovsdb_idl_txn; >> > +struct ovsrec_port_table; >> > +struct ovsrec_bridge; >> > +struct ovsrec_bridge_table; >> > +struct ovsrec_open_vswitch_table; >> > +struct sbrec_chassis; >> > +struct ovsrec_interface_table; >> > +struct ovsrec_mirror_table; >> > +struct sbrec_mirror_table; >> > +struct sbrec_port_binding_table; >> > + >> > +struct port_mirror_ctx { >> > + struct shash *ovs_mirrors; >> > + struct ovsdb_idl_txn *ovs_idl_txn; >> > + const struct ovsrec_port_table *port_table; >> > + const struct ovsrec_bridge *br_int; >> > + const struct sbrec_chassis *chassis_rec; >> > + const struct ovsrec_bridge_table *bridge_table; >> > + const struct ovsrec_open_vswitch_table *ovs_table; >> > + const struct ovsrec_interface_table *iface_table; >> > + const struct ovsrec_mirror_table *mirror_table; >> > + const struct sbrec_mirror_table *sb_mirror_table; >> > + const struct sbrec_port_binding_table *port_binding_table; >> > + struct shash *local_bindings; >> > +}; >> > + >> > +void mirror_register_ovs_idl(struct ovsdb_idl *); >> > +void ovn_port_mirror_init(struct shash *); >> > +void ovn_port_mirror_destroy(struct shash *); >> > +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx); >> > +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, >> > + bool removed, >> > + struct port_mirror_ctx *pm_ctx); >> > +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx); >> > +#endif >> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c >> > index bc2c5ab63..43e71ea3e 100644 >> > --- a/controller/ovn-controller.c >> > +++ b/controller/ovn-controller.c >> > @@ -77,6 +77,7 @@ >> > #include "stopwatch.h" >> > #include "lib/inc-proc-eng.h" >> > #include "hmapx.h" >> > +#include "mirror.h" >> > >> > VLOG_DEFINE_THIS_MODULE(main); >> > >> > @@ -969,6 +970,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 \ >> > @@ -989,6 +991,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 { >> > @@ -1006,7 +1009,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, >> > @@ -2386,6 +2390,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. >> > @@ -3645,6 +3846,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 >> > @@ -3801,6 +4003,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, >> > @@ -4070,34 +4288,36 @@ main(int argc, char *argv[]) >> > >> > stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME, >> > time_msec()); >> > - if (ovnsb_idl_txn) { >> > - if (ofctrl_has_backlog()) { >> > - /* When there are in-flight messages pending >> > to >> > - * ovs-vswitchd, we should hold on >> > recomputing so >> > - * that the previous flow installations won't >> > be >> > - * delayed. However, we still want to try if >> > - * recompute is not needed and we can quickly >> > - * incrementally process the new changes, to >> > avoid >> > - * unnecessarily forced recomputes later on. >> > This >> > - * is because the OVSDB change tracker cannot >> > - * preserve tracked changes across >> > iterations. If >> > - * change tracking is improved, we can simply >> > skip >> > - * this round of engine_run and continue >> > processing >> > - * acculated changes incrementally later when >> > - * ofctrl_has_backlog() returns false. */ >> > - engine_run(false); >> > - } else { >> > - engine_run(true); >> > - } >> > - } else { >> > - /* Even if there's no SB DB transaction available, >> > + >> > + bool allow_engine_recompute = true; >> > + >> > + if (!ovnsb_idl_txn || !ovs_idl_txn || >> > + >> > ofctrl_has_backlog()) { >> > + /* When there are in-flight messages pending to >> > + * ovs-vswitchd, we should hold on recomputing so >> > + * that the previous flow installations won't be >> > + * delayed. However, we still want to try if >> > + * recompute is not needed and we can quickly >> > + * incrementally process the new changes, to avoid >> > + * unnecessarily forced recomputes later on. This >> > + * is because the OVSDB change tracker cannot >> > + * preserve tracked changes across iterations. If >> > + * change tracking is improved, we can simply skip >> > + * this round of engine_run and continue >> > processing >> > + * acculated changes incrementally later when >> > + * ofctrl_has_backlog() returns false. */ >> > + >> > + /* Even if there's no SB/OVS DB transaction >> > available, >> > * try to run the engine so that we can handle any >> > * incremental changes that don't require a >> > recompute. >> > * If a recompute is required, the engine will >> > abort, >> > * triggerring a full run in the next iteration. >> > */ >> > - engine_run(false); >> > + allow_engine_recompute = false; >> > } >> > + >> > + engine_run(allow_engine_recompute); >> > + >> > stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME, >> > time_msec()); >> > if (engine_has_updated()) { >> > diff --git a/northd/en-northd.c b/northd/en-northd.c >> > index 7fe83db64..608220b1f 100644 >> > --- a/northd/en-northd.c >> > +++ b/northd/en-northd.c >> > @@ -80,6 +80,8 @@ void en_northd_run(struct engine_node *node, void *data) >> > EN_OVSDB_GET(engine_get_input("NB_acl", node)); >> > input_data.nbrec_static_mac_binding_table = >> > EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node)); >> > + input_data.nbrec_mirror_table = >> > + EN_OVSDB_GET(engine_get_input("NB_mirror", node)); >> > >> > input_data.sbrec_sb_global_table = >> > EN_OVSDB_GET(engine_get_input("SB_sb_global", node)); >> > @@ -113,6 +115,8 @@ void en_northd_run(struct engine_node *node, void >> > *data) >> > EN_OVSDB_GET(engine_get_input("SB_chassis_private", node)); >> > input_data.sbrec_static_mac_binding_table = >> > EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node)); >> > + input_data.sbrec_mirror_table = >> > + EN_OVSDB_GET(engine_get_input("SB_mirror", node)); >> > >> > northd_run(&input_data, data, >> > eng_ctx->ovnnb_idl_txn, >> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c >> > index 54e0ad3b0..ac27a730e 100644 >> > --- a/northd/inc-proc-northd.c >> > +++ b/northd/inc-proc-northd.c >> > @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd); >> > NB_NODE(acl, "acl") \ >> > NB_NODE(logical_router, "logical_router") \ >> > NB_NODE(qos, "qos") \ >> > + NB_NODE(mirror, "mirror") \ >> > NB_NODE(meter, "meter") \ >> > NB_NODE(meter_band, "meter_band") \ >> > NB_NODE(logical_router_port, "logical_router_port") \ >> > @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd); >> > SB_NODE(logical_flow, "logical_flow") \ >> > SB_NODE(logical_dp_group, "logical_DP_group") \ >> > SB_NODE(multicast_group, "multicast_group") \ >> > + SB_NODE(mirror, "mirror") \ >> > SB_NODE(meter, "meter") \ >> > SB_NODE(meter_band, "meter_band") \ >> > SB_NODE(datapath_binding, "datapath_binding") \ >> > @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, >> > engine_add_input(&en_northd, &en_nb_acl, NULL); >> > engine_add_input(&en_northd, &en_nb_logical_router, NULL); >> > engine_add_input(&en_northd, &en_nb_qos, NULL); >> > + engine_add_input(&en_northd, &en_nb_mirror, NULL); >> > engine_add_input(&en_northd, &en_nb_meter, NULL); >> > engine_add_input(&en_northd, &en_nb_meter_band, NULL); >> > engine_add_input(&en_northd, &en_nb_logical_router_port, NULL); >> > @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, >> > engine_add_input(&en_northd, &en_sb_address_set, NULL); >> > engine_add_input(&en_northd, &en_sb_port_group, NULL); >> > engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL); >> > + engine_add_input(&en_northd, &en_sb_mirror, NULL); >> > engine_add_input(&en_northd, &en_sb_meter, NULL); >> > engine_add_input(&en_northd, &en_sb_meter_band, NULL); >> > engine_add_input(&en_northd, &en_sb_datapath_binding, NULL); >> > diff --git a/northd/northd.c b/northd/northd.c >> > index 84440a47f..5880ddc69 100644 >> > --- a/northd/northd.c >> > +++ b/northd/northd.c >> > @@ -3220,6 +3220,89 @@ ovn_port_update_sbrec_chassis( >> > free(requested_chassis_sb); >> > } >> > >> > +static void >> > +do_sb_mirror_addition(struct northd_input *input_data, >> > + const struct ovn_port *op) >> > +{ >> > + for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) { >> > + const struct sbrec_mirror *sb_mirror; >> > + SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, >> > + input_data->sbrec_mirror_table) { >> > + if (!strcmp(sb_mirror->name, >> > + op->nbsp->mirror_rules[i]->name)) { >> > + /* Add the value to SB */ >> > + sbrec_port_binding_update_mirror_rules_addvalue(op->sb, >> > + >> > sb_mirror); >> > + } >> > + } >> > + } >> > +} >> > + >> > +static void >> > +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data, >> > + const struct ovn_port *op) >> > +{ >> > + size_t i = 0; >> > + if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) { >> > + /* Needs deletion in SB */ >> > + struct shash nb_mirror_rules = >> > SHASH_INITIALIZER(&nb_mirror_rules); >> > + for (i = 0; i < op->nbsp->n_mirror_rules; i++) { >> > + shash_add(&nb_mirror_rules, >> > + op->nbsp->mirror_rules[i]->name, >> > + op->nbsp->mirror_rules[i]); >> > + } >> > + >> > + for (i = 0; i < op->sb->n_mirror_rules; i++) { >> > + if (!shash_find(&nb_mirror_rules, >> > + op->sb->mirror_rules[i]->name)) { >> > + >> > sbrec_port_binding_update_mirror_rules_delvalue(op->sb, >> > + op->sb->mirror_rules[i]); >> > + } >> > + } >> >> There should also be the case of "present in nb but not in sb" here. >> Remember, a single transaction may include any number of modifications >> to the list. Both deletions and additions. So I don't think you should >> even have separate branches (<, >, ==), the same code may handle it >> consistently. (I think the current == branch could handle it already.) > > > Yes, "present in nb but not in sb" is already there. See the else if block > below > with " } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) {" > > Yes, a single transaction swapping the mirrors which you told is handled in > == case. > > Further, I feel it might not be too efficient to keep it common. > Because with checking ( > or <) we are doing high level and taking action > In case it hits the == we do more work to find out more if something needs to > be done or not. > So, I feel let's keep these 3 separate. >> >> >> > + >> > + struct shash_node *node, *next; >> > + SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) { >> > + shash_delete(&nb_mirror_rules, node); >> > + } >> > + shash_destroy(&nb_mirror_rules); >> > + >> > + } else if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) { >> > + /* Needs addition in SB */ >> > + do_sb_mirror_addition(input_data, op); >> > + } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) { >> > + /* >> > + * Check if its the same mirrors on both SB and NB DBs >> > + * If not update accordingly. >> > + */ >> > + bool needs_sb_addition = false; >> > + struct shash nb_mirror_rules = >> > SHASH_INITIALIZER(&nb_mirror_rules); >> > + for (i = 0; i < op->nbsp->n_mirror_rules; i++) { >> > + shash_add(&nb_mirror_rules, >> > + op->nbsp->mirror_rules[i]->name, >> > + op->nbsp->mirror_rules[i]); >> > + } >> > + >> > + for (i = 0; i < op->sb->n_mirror_rules; i++) { >> > + if (!shash_find(&nb_mirror_rules, >> > + op->sb->mirror_rules[i]->name)) { >> > + >> > sbrec_port_binding_update_mirror_rules_delvalue(op->sb, >> > + op->sb->mirror_rules[i]); >> > + needs_sb_addition = true; >> > + } >> > + } >> > + >> > + struct shash_node *node, *next; >> > + SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) { >> > + shash_delete(&nb_mirror_rules, node); >> > + } >> > + shash_destroy(&nb_mirror_rules); >> > + >> > + if (needs_sb_addition) { >> > + do_sb_mirror_addition(input_data, op); >> > + } >> > + } >> > +} >> > + >> > static void >> > ovn_port_update_sbrec(struct northd_input *input_data, >> > struct ovsdb_idl_txn *ovnsb_txn, >> > @@ -3579,6 +3662,15 @@ ovn_port_update_sbrec(struct northd_input >> > *input_data, >> > } >> > sbrec_port_binding_set_external_ids(op->sb, &ids); >> > smap_destroy(&ids); >> > + >> > + if (!op->nbsp->n_mirror_rules) { >> > + /* Nothing is set. Clear mirror_rules from pb. */ >> > + sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0); >> > + } else { >> > + /* Check if SB DB update needed */ >> > + sbrec_port_binding_update_mirror_rules(input_data, op); >> > + } >> > + >> > } >> > if (op->tunnel_key != op->sb->tunnel_key) { >> > sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); >> > @@ -14956,6 +15048,85 @@ sync_meters(struct northd_input *input_data, >> > shash_destroy(&sb_meters); >> > } >> > >> > +static bool >> > +mirror_needs_update(const struct nbrec_mirror *nb_mirror, >> > + const struct sbrec_mirror *sb_mirror) >> > +{ >> > + >> > + if (nb_mirror->index != sb_mirror->index) { >> > + return true; >> > + } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) { >> > + return true; >> > + } else if (strcmp(nb_mirror->type, sb_mirror->type)) { >> > + return true; >> > + } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) { >> > + return true; >> > + } >> > + >> > + return false; >> > +} >> > + >> > +static void >> > +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn, >> > + const char *mirror_name, >> > + const struct nbrec_mirror *nb_mirror, >> > + struct shash *sb_mirrors, >> > + struct sset *used_sb_mirrors) >> > +{ >> > + const struct sbrec_mirror *sb_mirror; >> > + bool new_sb_mirror = false; >> > + >> > + sb_mirror = shash_find_data(sb_mirrors, mirror_name); >> > + if (!sb_mirror) { >> > + sb_mirror = sbrec_mirror_insert(ovnsb_txn); >> > + sbrec_mirror_set_name(sb_mirror, mirror_name); >> > + shash_add(sb_mirrors, sb_mirror->name, sb_mirror); >> > + new_sb_mirror = true; >> > + } >> > + sset_add(used_sb_mirrors, mirror_name); >> > + >> > + if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) { >> > + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter); >> > + sbrec_mirror_set_index(sb_mirror, nb_mirror->index); >> > + sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink); >> > + sbrec_mirror_set_type(sb_mirror, nb_mirror->type); >> > + } >> > +} >> > + >> > +static void >> > +sync_mirrors(struct northd_input *input_data, >> > + struct ovsdb_idl_txn *ovnsb_txn) >> > +{ >> > + struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors); >> > + struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors); >> > + >> > + const struct sbrec_mirror *sb_mirror; >> > + SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, >> > input_data->sbrec_mirror_table) { >> > + shash_add(&sb_mirrors, sb_mirror->name, sb_mirror); >> > + } >> > + >> > + const struct nbrec_mirror *nb_mirror; >> > + NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, >> > input_data->nbrec_mirror_table) { >> > + sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, >> > nb_mirror, >> > + &sb_mirrors, &used_sb_mirrors); >> > + } >> > + >> > + const char *used_mirror; >> > + const char *used_mirror_next; >> > + SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) { >> > + shash_find_and_delete(&sb_mirrors, used_mirror); >> > + sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror)); >> > + } >> > + sset_destroy(&used_sb_mirrors); >> > + >> > + struct shash_node *node, *next; >> > + SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) { >> > + sbrec_mirror_delete(node->data); >> > + shash_delete(&sb_mirrors, node); >> > + } >> > + shash_destroy(&sb_mirrors); >> > +} >> > + >> > /* >> > * struct 'dns_info' is used to sync the DNS records between OVN >> > Northbound db >> > * and Southbound db. >> > @@ -15583,6 +15754,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 aa9a3ae6e..f5ed45e20 100644 >> > --- a/northd/northd.h >> > +++ b/northd/northd.h >> > @@ -36,6 +36,7 @@ struct northd_input { >> > const struct nbrec_acl_table *nbrec_acl_table; >> > const struct nbrec_static_mac_binding_table >> > *nbrec_static_mac_binding_table; >> > + const struct nbrec_mirror_table *nbrec_mirror_table; >> > >> > /* Southbound table references */ >> > const struct sbrec_sb_global_table *sbrec_sb_global_table; >> > @@ -55,6 +56,7 @@ struct northd_input { >> > const struct sbrec_chassis_private_table *sbrec_chassis_private_table; >> > const struct sbrec_static_mac_binding_table >> > *sbrec_static_mac_binding_table; >> > + const struct sbrec_mirror_table *sbrec_mirror_table; >> > >> > /* Indexes */ >> > struct ovsdb_idl_index *sbrec_chassis_by_name; >> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema >> > index 174364c8b..01de55222 100644 >> > --- a/ovn-nb.ovsschema >> > +++ b/ovn-nb.ovsschema >> > @@ -1,7 +1,7 @@ >> > { >> > "name": "OVN_Northbound", >> > - "version": "6.3.0", >> > - "cksum": "4042813038 31869", >> > + "version": "6.4.0", >> > + "cksum": "589874483 33352", >> > "tables": { >> > "NB_Global": { >> > "columns": { >> > @@ -132,6 +132,11 @@ >> > "refType": "weak"}, >> > "min": 0, >> > "max": 1}}, >> > + "mirror_rules": {"type": {"key": {"type": "uuid", >> > + "refTable": "Mirror", >> > + "refType": "weak"}, >> > + "min": 0, >> > + "max": "unlimited"}}, >> > "ha_chassis_group": { >> > "type": {"key": {"type": "uuid", >> > "refTable": "HA_Chassis_Group", >> > @@ -301,6 +306,28 @@ >> > "type": {"key": "string", "value": "string", >> > "min": 0, "max": "unlimited"}}}, >> > "isRoot": false}, >> > + "Mirror": { >> > + "columns": { >> > + "name": {"type": "string"}, >> > + "filter": {"type": {"key": {"type": "string", >> > + "enum": ["set", ["from-lport", >> > + "to-lport", >> > + "both"]]}}}, >> > + "sink":{"type": "string"}, >> > + "type": {"type": {"key": {"type": "string", >> > + "enum": ["set", ["gre", >> > + >> > "erspan"]]}}}, >> > + "index": {"type": "integer"}, >> > + "src": {"type": {"key": {"type": "uuid", >> > + "refTable": >> > "Logical_Switch_Port", >> > + "refType": "weak"}, >> > + "min": 0, >> > + "max": "unlimited"}}, >> > + "external_ids": { >> > + "type": {"key": "string", "value": "string", >> > + "min": 0, "max": "unlimited"}}}, >> > + "indexes": [["name"]], >> > + "isRoot": true}, >> > "Meter": { >> > "columns": { >> > "name": {"type": "string"}, >> > diff --git a/ovn-nb.xml b/ovn-nb.xml >> > index f41e9d7c0..8e42c6bec 100644 >> > --- a/ovn-nb.xml >> > +++ b/ovn-nb.xml >> > @@ -1554,6 +1554,11 @@ >> > </column> >> > </group> >> > >> > + <column name="mirror_rules"> >> > + Mirror rules that apply to logical switch port which is the >> > source. >> > + Please see the <ref table="Mirror"/> table. >> > + </column> >> > + >> > <column name="ha_chassis_group"> >> > References a row in the OVN Northbound database's >> > <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table. >> > @@ -2491,6 +2496,64 @@ >> > </column> >> > </table> >> > >> > + <table name="Mirror" title="Mirror Entry"> >> > + <p> >> > + Each row in this table represents one Mirror that can be used for >> > + port mirroring. These Mirrors are referenced by the >> > + <ref column="mirror_rules" table="Logical_Switch_Port"/> column in >> > + the <ref table="Logical_Switch_Port"/> table. >> > + </p> >> > + >> > + <column name="name"> >> > + <p> >> > + Represents the name of the mirror. >> > + </p> >> > + </column> >> > + >> > + <column name="filter"> >> > + <p> >> > + The value of this field represents selection criteria of the >> > mirror. >> > + Supported values for filter to-lport / from-lport / both >> > + to-lport - to mirror packets coming into logical port >> > + from-lport - to mirror packets going out of logical port >> > + both - to mirror packets coming into and going out of logical >> > port. >> > + </p> >> > + </column> >> > + >> > + <column name="sink"> >> > + <p> >> > + The value of this field represents the destination/sink of the >> > mirror. >> > + The value it takes is an IP address of the sink port. >> > + </p> >> > + </column> >> > + >> > + <column name="type"> >> > + <p> >> > + The value of this field represents the type of the tunnel used for >> > + sending the mirrored packets. Supported Tunnel types GRE and >> > ERSPAN >> >> I think we should document the exact type names to use here. ("gre" >> and "erspan") These are currently not clear from the text (one could >> think that CAPS names should used.) > > Sure, Will document it clearly. >> >> >> > + </p> >> > + </column> >> > + >> > + <column name="index"> >> > + <p> >> > + The value of this field represents the tunnel ID. Depending on the >> > + tunnel type configured, GRE key value if type GRE and erspan_idx >> > value >> > + if ERSPAN >> > + </p> >> > + </column> >> > + >> > + <column name="src"> >> > + <p> >> > + The value of this field represents a list of source ports for the >> > + mirror. Please see the <ref table="Logical_Switch_Port"/> table. >> > + </p> >> > + </column> >> > + >> > + <column name="external_ids"> >> > + See <em>External IDs</em> at the beginning of this document. >> > + </column> >> > + </table> >> > + >> > <table name="Meter" title="Meter entry"> >> > <p> >> > Each row in this table represents a meter that can be used for QoS >> > or >> > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema >> > index 576ebbdeb..b83134416 100644 >> > --- a/ovn-sb.ovsschema >> > +++ b/ovn-sb.ovsschema >> > @@ -1,7 +1,7 @@ >> > { >> > "name": "OVN_Southbound", >> > - "version": "20.25.0", >> > - "cksum": "53184112 28845", >> > + "version": "20.26.0", >> > + "cksum": "2344151793 30004", >> > "tables": { >> > "SB_Global": { >> > "columns": { >> > @@ -142,6 +142,23 @@ >> > "indexes": [["datapath", "tunnel_key"], >> > ["datapath", "name"]], >> > "isRoot": true}, >> > + "Mirror": { >> > + "columns": { >> > + "name": {"type": "string"}, >> > + "filter": {"type": {"key": {"type": "string", >> > + "enum": ["set", >> > + ["from-lport", >> > + >> > "to-lport","both"]]}}}, >> > + "sink":{"type": "string"}, >> > + "type": {"type": {"key": {"type": "string", >> > + "enum": ["set", >> > + ["gre", >> > "erspan"]]}}}, >> > + "index": {"type": "integer"}, >> > + "external_ids": { >> > + "type": {"key": "string", "value": "string", >> > + "min": 0, "max": "unlimited"}}}, >> > + "indexes": [["name"]], >> > + "isRoot": true}, >> > "Meter": { >> > "columns": { >> > "name": {"type": "string"}, >> > @@ -230,6 +247,11 @@ >> > "refTable": "Encap", >> > "refType": "weak"}, >> > "min": 0, "max": "unlimited"}}, >> > + "mirror_rules": {"type": {"key": {"type": "uuid", >> > + "refTable": "Mirror", >> > + "refType": "weak"}, >> > + "min": 0, >> > + "max": "unlimited"}}, >> > "mac": {"type": {"key": "string", >> > "min": 0, >> > "max": "unlimited"}}, >> > diff --git a/ovn-sb.xml b/ovn-sb.xml >> > index 315d60853..05c0db6b4 100644 >> > --- a/ovn-sb.xml >> > +++ b/ovn-sb.xml >> > @@ -2742,6 +2742,51 @@ tcp.flags = RST; >> > </column> >> > </table> >> > >> > + <table name="Mirror" title="Mirror Entry"> >> > + <p> >> > + Each row in this table represents one Mirror that can be used for >> > + port mirroring. These Mirrors are referenced by the >> > + <ref column="mirror_rules" table="Port_Binding"/> column in >> > + the <ref table="Port_Binding"/> table. >> > + </p> >> > + >> > + <column name="name"> >> > + <p> >> > + Represents the name of the mirror. >> > + </p> >> > + </column> >> > + >> > + <column name="filter"> >> > + <p> >> > + The value of this field represents selection criteria of the >> > mirror. >> > + </p> >> > + </column> >> > + >> > + <column name="sink"> >> > + <p> >> > + The value of this field represents the destination/sink of the >> > mirror. >> > + </p> >> > + </column> >> > + >> > + <column name="type"> >> > + <p> >> > + The value of this field represents the type of the tunnel used for >> > + sending the mirrored packets >> > + </p> >> > + </column> >> > + >> > + <column name="index"> >> > + <p> >> > + The value of this field represents the key/idx depending on the >> > + tunnel type configured >> > + </p> >> > + </column> >> > + >> > + <column name="external_ids"> >> > + See <em>External IDs</em> at the beginning of this document. >> > + </column> >> > + </table> >> > + >> > <table name="Meter" title="Meter entry"> >> > <p> >> > Each row in this table represents a meter that can be used for QoS >> > or >> > @@ -3244,6 +3289,11 @@ tcp.flags = RST; >> > </column> >> > </group> >> > >> > + <column name="mirror_rules"> >> > + Mirror rules that apply to the port binding. >> > + Please see the <ref table="Mirror"/> table. >> > + </column> >> > + >> > <group title="Patch Options"> >> > <p> >> > These options apply to logical ports with <ref column="type"/> of >> > diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at >> > index 6a5d52a5d..a9c0853f5 100644 >> > --- a/tests/ovn-nbctl.at >> > +++ b/tests/ovn-nbctl.at >> > @@ -435,6 +435,122 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl >> > >> > dnl --------------------------------------------------------------------- >> > >> > +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [ >> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1]) >> > +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2]) >> > +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3]) >> > +AT_CHECK([ovn-nbctl ls-add sw0]) >> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1]) >> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2]) >> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3]) >> > + >> > +dnl Add duplicate mirror name >> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], >> > [], [stderr]) >> > +AT_CHECK([grep 'already exists' stderr], [0], [ignore]) >> > + >> > +dnl Attach invalid source port to mirror >> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], >> > [stderr]) >> > +AT_CHECK([grep 'port name not found' stderr], [0], [ignore]) >> > + >> > +dnl Attach source port to mirror >> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3]) >> > + >> > +dnl Attach one more source port to mirror >> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3]) >> > + >> > +dnl Verify if multiple ports are attached to the same mirror properly >> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >> > +mirror1: >> > + Type : gre >> > + Sink : 10.10.10.1 >> > + Filter : from-lport >> > + Index/Key: 0 >> > + Sources : None attached >> > +mirror2: >> > + Type : erspan >> > + Sink : 10.10.10.2 >> > + Filter : both >> > + Index/Key: 1 >> > + Sources : None attached >> > +mirror3: >> > + Type : gre >> > + Sink : 10.10.10.3 >> > + Filter : to-lport >> > + Index/Key: 2 >> > + Sources : sw0-port1 sw0-port3 >> > +]) >> > + >> > +dnl Detach one source port from mirror >> > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3]) >> > + >> > +dnl Verify if detach source port from mirror happens properly >> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >> > +mirror1: >> > + Type : gre >> > + Sink : 10.10.10.1 >> > + Filter : from-lport >> > + Index/Key: 0 >> > + Sources : None attached >> > +mirror2: >> > + Type : erspan >> > + Sink : 10.10.10.2 >> > + Filter : both >> > + Index/Key: 1 >> > + Sources : None attached >> > +mirror3: >> > + Type : gre >> > + Sink : 10.10.10.3 >> > + Filter : to-lport >> > + Index/Key: 2 >> > + Sources : sw0-port1 >> > +]) >> > + >> > +dnl Delete a single mirror which has source attached. >> > +AT_CHECK([ovn-nbctl mirror-del mirror3]) >> > + >> > +dnl Check if the detach happened from source properly >> > +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules | cut >> > -b 3], [0], [dnl >> > + >> > +]) >> > + >> > +dnl Check if the mirror deleted properly >> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >> > +mirror1: >> > + Type : gre >> > + Sink : 10.10.10.1 >> > + Filter : from-lport >> > + Index/Key: 0 >> > + Sources : None attached >> > +mirror2: >> > + Type : erspan >> > + Sink : 10.10.10.2 >> > + Filter : both >> > + Index/Key: 1 >> > + Sources : None attached >> > +]) >> > + >> > +dnl Delete another mirror >> > +AT_CHECK([ovn-nbctl mirror-del mirror2]) >> > + >> > +dnl Update the Sink address >> > +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13]) >> > + >> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >> > +mirror1: >> > + Type : gre >> > + Sink : 192.168.1.13 >> > + Filter : from-lport >> > + Index/Key: 0 >> > + Sources : None attached >> > +]) >> > + >> > +dnl Delete all mirrors >> > +AT_CHECK([ovn-nbctl mirror-del]) >> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >> > +])]) >> > + >> > +dnl --------------------------------------------------------------------- >> > + >> > OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [ >> > AT_CHECK([ovn-nbctl lr-add lr0]) >> > AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [], >> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at >> > index 093e01c6d..fd8d98cbf 100644 >> > --- a/tests/ovn-northd.at >> > +++ b/tests/ovn-northd.at >> > @@ -2319,6 +2319,108 @@ check_meter_by_name NOT meter_me__${acl1} >> > meter_me__${acl2} >> > AT_CLEANUP >> > ]) >> > >> > +OVN_FOR_EACH_NORTHD([ >> > +AT_SETUP([Check NB-SB mirrors sync]) >> > +AT_KEYWORDS([mirrors]) >> > +ovn_start >> > + >> > +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2 >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl >> > +"10.10.10.2" >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl >> > +erspan >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl >> > +0 >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl >> > +both >> > +]) >> > + >> > +check ovn-nbctl --wait=sb \ >> > + -- set mirror . sink=192.168.1.13 >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl >> > +"192.168.1.13" >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl >> > +erspan >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl >> > +0 >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl >> > +both >> > +]) >> > + >> > +check ovn-nbctl --wait=sb \ >> > + -- set mirror . type=gre >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl >> > +gre >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl >> > +"192.168.1.13" >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl >> > +0 >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl >> > +both >> > +]) >> > + >> > +check ovn-nbctl --wait=sb \ >> > + -- set mirror . index=12 >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl >> > +12 >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl >> > +gre >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl >> > +"192.168.1.13" >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl >> > +both >> > +]) >> > + >> > +check ovn-nbctl --wait=sb \ >> > + -- set mirror . filter=to-lport >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl >> > +to-lport >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl >> > +12 >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl >> > +gre >> > +]) >> > + >> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl >> > +"192.168.1.13" >> > +]) >> > + >> > +AT_CLEANUP >> > +]) >> > + >> > OVN_FOR_EACH_NORTHD([ >> > AT_SETUP([ACL skip hints for stateless config]) >> > AT_KEYWORDS([acl]) >> > diff --git a/tests/ovn.at b/tests/ovn.at >> > index 07f72dc31..42f28ab1b 100644 >> > --- a/tests/ovn.at >> > +++ b/tests/ovn.at >> > @@ -16121,6 +16121,237 @@ 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 >> > + >> > +AT_CHECK([as hv1 ovs-vsctl get Mirror mirror0 name], [0], [dnl >> > +mirror0 >> > +]) >> > + >> > +# Delete vif1 so that OVN releases port binding >> > +check ovs-vsctl del-port br-int vif1 >> > +check ovn-nbctl --wait=hv sync >> > + >> > +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl >> > +]) >> > + >> > +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 811468dc6..7d593b726 100644 >> > --- a/utilities/ovn-nbctl.c >> > +++ b/utilities/ovn-nbctl.c >> > @@ -271,6 +271,19 @@ QoS commands:\n\ >> > remove QoS rules from SWITCH\n\ >> > qos-list SWITCH print QoS rules for SWITCH\n\ >> > \n\ >> > +Mirror commands:\n\ >> > + mirror-add NAME TYPE INDEX FILTER IP\n\ >> > + add a mirror with given name\n\ >> > + specify TYPE 'gre' or 'erspan'\n\ >> > + specify the tunnel INDEX value\n\ >> > + (indicates key if GRE\n\ >> > + erpsan_idx if ERSPAN)\n\ >> > + specify FILTER for mirroring selection\n\ >> > + 'to-lport' / 'from-lport' / 'both'\n\ >> > + specify Sink / Destination i.e. Remote IP\n\ >> > + mirror-del [NAME] remove mirrors\n\ >> > + mirror-list print mirrors\n\ >> > +\n\ >> > Meter commands:\n\ >> > [--fair]\n\ >> > meter-add NAME ACTION RATE UNIT [BURST]\n\ >> > @@ -311,6 +324,8 @@ Logical switch port commands:\n\ >> > set dhcpv6 options for PORT\n\ >> > lsp-get-dhcpv6-options PORT get the dhcpv6 options for PORT\n\ >> > lsp-get-ls PORT get the logical switch which the port belongs >> > to\n\ >> > + lsp-attach-mirror PORT MIRROR attach source PORT to MIRROR\n\ >> > + lsp-detach-mirror PORT MIRROR detach source PORT from MIRROR\n\ >> > \n\ >> > Forwarding group commands:\n\ >> > [--liveness]\n\ >> > @@ -1685,6 +1700,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx) >> > ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type); >> > } >> > >> > +static void >> > +nbctl_pre_lsp_mirror(struct ctl_context *ctx) >> > +{ >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name); >> > + ovsdb_idl_add_column(ctx->idl, >> > + &nbrec_logical_switch_port_col_mirror_rules); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src); >> > +} >> > + >> > +static int >> > +mirror_cmp(const void *mirror1_, const void *mirror2_) >> > +{ >> > + const struct nbrec_mirror *const *mirror_1 = mirror1_; >> > + const struct nbrec_mirror *const *mirror_2 = mirror2_; >> > + >> > + const struct nbrec_mirror *mirror1 = *mirror_1; >> > + const struct nbrec_mirror *mirror2 = *mirror_2; >> > + >> > + return strcmp(mirror1->name,mirror2->name); >> > +} >> > + >> > +static char * OVS_WARN_UNUSED_RESULT >> > +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id, >> > + bool must_exist, >> > + const struct nbrec_mirror **mirror_p) >> > +{ >> > + const struct nbrec_mirror *mirror = NULL; >> > + *mirror_p = NULL; >> > + >> > + struct uuid mirror_uuid; >> > + bool is_uuid = uuid_from_string(&mirror_uuid, id); >> > + if (is_uuid) { >> > + mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid); >> > + } >> > + >> > + if (!mirror) { >> > + NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) { >> > + if (!strcmp(mirror->name, id)) { >> > + break; >> > + } >> > + } >> > + } >> > + >> > + if (!mirror && must_exist) { >> > + return xasprintf("%s: mirror %s not found", >> > + id, is_uuid ? "UUID" : "name"); >> > + } >> > + >> > + *mirror_p = mirror; >> > + return NULL; >> > +} >> > + >> > +static void >> > +nbctl_lsp_attach_mirror(struct ctl_context *ctx) >> > +{ >> > + const char *port = ctx->argv[1]; >> > + const char *mirror_name = ctx->argv[2]; >> > + const struct nbrec_logical_switch_port *lsp = NULL; >> > + const struct nbrec_mirror *mirror; >> > + >> > + char *error; >> > + >> > + error = lsp_by_name_or_uuid(ctx, port, true, &lsp); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + >> > + /*check if a mirror rule actually exists on that name or not*/ >> > + error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + /* Check if same mirror rule already exists for the lsp */ >> > + for (size_t i = 0; i < lsp->n_mirror_rules; i++) { >> > + if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) { >> > + bool may_exist = shash_find(&ctx->options, "--may-exist") != >> > NULL; >> > + if (!may_exist) { >> > + ctl_error(ctx, "Same mirror already existed on the lsp >> > %s.", >> > + ctx->argv[1]); >> > + return; >> > + } >> > + return; >> > + } >> > + } >> > + >> > + nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror); >> > + nbrec_mirror_update_src_addvalue(mirror,lsp); >> > + >> > +} >> > + >> > +static void >> > +nbctl_lsp_detach_mirror(struct ctl_context *ctx) >> > +{ >> > + const char *port = ctx->argv[1]; >> > + const char *mirror_name = ctx->argv[2]; >> > + const struct nbrec_logical_switch_port *lsp = NULL; >> > + const struct nbrec_mirror *mirror; >> > + >> > + char *error; >> > + >> > + error = lsp_by_name_or_uuid(ctx, port, true, &lsp); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + >> > + /*check if a mirror rule actually exists on that name or not*/ >> > + error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror); >> > + nbrec_mirror_update_src_delvalue(mirror,lsp); >> > + >> > +} >> > + >> > static void >> > nbctl_lsp_set_type(struct ctl_context *ctx) >> > { >> > @@ -7241,6 +7380,211 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context >> > *ctx) >> > nbrec_ha_chassis_set_priority(ha_chassis, priority); >> > } >> > >> > +static char * OVS_WARN_UNUSED_RESULT >> > +parse_filter(const char *arg, const char **selection_p) >> > +{ >> > + /* Validate selection. Only require the first letter. */ >> > + if (arg[0] == 't') { >> > + *selection_p = "to-lport"; >> > + } else if (arg[0] == 'f') { >> > + *selection_p = "from-lport"; >> > + } else if (arg[0] == 'b') { >> > + *selection_p = "both"; >> > + } else { >> > + *selection_p = NULL; >> > + return xasprintf("%s: selection must be \"to-lport\" or " >> > + "\"from-lport\" or \"both\" ", arg); >> > + } >> > + return NULL; >> > +} >> > + >> > +static char * OVS_WARN_UNUSED_RESULT >> > +parse_type(const char *arg, const char **type_p) >> > +{ >> > + /* Validate type. Only require the first letter. */ >> > + if (arg[0] == 'g') { >> > + *type_p = "gre"; >> > + } else if (arg[0] == 'e') { >> > + *type_p = "erspan"; >> > + } else { >> > + *type_p = NULL; >> > + return xasprintf("%s: type must be \"gre\" or " >> > + "\"erspan\"", arg); >> > + } >> > + return NULL; >> > +} >> > + >> > +static void >> > +nbctl_pre_mirror_add(struct ctl_context *ctx) >> > +{ >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type); >> > +} >> > + >> > +static void >> > +nbctl_mirror_add(struct ctl_context *ctx) >> > +{ >> > + const char *filter = NULL; >> > + const char *sink_ip = NULL; >> > + const char *type = NULL; >> > + const char *name = NULL; >> > + char *new_sink_ip = NULL; >> > + int64_t index; >> > + char *error = NULL; >> > + const struct nbrec_mirror *mirror_check = NULL; >> > + >> > + /* Mirror Name */ >> > + name = ctx->argv[1]; >> > + NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) { >> > + if (!strcmp(mirror_check->name, name)) { >> > + ctl_error(ctx, "Mirror with %s name already exists.", >> > + name); >> > + return; >> > + } >> > + } >> > + >> > + /* Tunnel Type - GRE/ERSPAN */ >> > + error = parse_type(ctx->argv[2], &type); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + /* tunnel index / GRE key / ERSPAN idx */ >> > + index = atoi(ctx->argv[3]); >> > + >> > + /* Filter for mirroring */ >> > + error = parse_filter(ctx->argv[4], &filter); >> > + if (error) { >> > + ctx->error = error; >> > + return; >> > + } >> > + >> > + /* Destination / Sink details */ >> > + sink_ip = ctx->argv[5]; >> > + >> > + /* check if it is a valid ip */ >> > + new_sink_ip = normalize_ipv4_addr_str(sink_ip); >> > + if (!new_sink_ip) { >> > + new_sink_ip = normalize_ipv6_addr_str(sink_ip); >> > + } >> > + >> > + if (new_sink_ip) { >> > + free(new_sink_ip); >> > + } else { >> > + ctl_error(ctx, "Invalid sink ip: %s", sink_ip); >> > + return; >> > + } >> > + >> > + /* Create the mirror. */ >> > + struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn); >> > + nbrec_mirror_set_name(mirror, name); >> > + nbrec_mirror_set_index(mirror, index); >> > + nbrec_mirror_set_filter(mirror, filter); >> > + nbrec_mirror_set_type(mirror, type); >> > + nbrec_mirror_set_sink(mirror, sink_ip); >> > + >> > +} >> > + >> > +static void >> > +nbctl_pre_mirror_del(struct ctl_context *ctx) >> > +{ >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src); >> > +} >> > + >> > +static void >> > +nbctl_mirror_del(struct ctl_context *ctx) >> > +{ >> > + const struct nbrec_mirror *mirror, *next; >> > + >> > + /* If a name is not specified, delete all mirrors. */ >> > + if (ctx->argc == 1) { >> > + NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) { >> > + nbrec_mirror_delete(mirror); >> > + } >> > + return; >> > + } >> > + >> > + /* Remove the matching mirror. */ >> > + NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) { >> > + if (strcmp(ctx->argv[1], mirror->name)) { >> > + continue; >> > + } >> > + nbrec_mirror_delete(mirror); >> > + return; >> > + } >> > +} >> > + >> > +static void >> > +nbctl_pre_mirror_list(struct ctl_context *ctx) >> > +{ >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type); >> > + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src); >> > +} >> > + >> > +static void >> > +nbctl_mirror_list(struct ctl_context *ctx) >> > +{ >> > + >> > + const struct nbrec_mirror **mirrors = NULL; >> > + const struct nbrec_mirror *mirror; >> > + size_t n_capacity = 0; >> > + size_t n_mirrors = 0; >> > + >> > + NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) { >> > + if (n_mirrors == n_capacity) { >> > + mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors); >> > + } >> > + >> > + mirrors[n_mirrors] = mirror; >> > + n_mirrors++; >> > + } >> > + >> > + if (n_mirrors) { >> > + qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp); >> > + } >> > + >> > + for (size_t i = 0; i < n_mirrors; i++) { >> > + mirror = mirrors[i]; >> > + ds_put_format(&ctx->output, "%s:\n", mirror->name); >> > + /* print all the values */ >> > + ds_put_format(&ctx->output, " Type : %s\n", mirror->type); >> > + ds_put_format(&ctx->output, " Sink : %s\n", mirror->sink); >> > + ds_put_format(&ctx->output, " Filter : %s\n", mirror->filter); >> > + ds_put_format(&ctx->output, " Index/Key: %ld\n", >> > + (long int)mirror->index); >> > + ds_put_cstr(&ctx->output, " Sources :"); >> > + if (mirror->n_src > 0) { >> > + struct svec srcs; >> > + const char *src; >> > + size_t j; >> > + svec_init(&srcs); >> > + for (j = 0; j < mirror->n_src; j++) { >> > + svec_add(&srcs, mirror->src[j]->name); >> > + } >> > + svec_sort(&srcs); >> > + SVEC_FOR_EACH (j, src, &srcs) { >> > + ds_put_format(&ctx->output, " %s", src); >> > + } >> > + svec_destroy(&srcs); >> > + } else { >> > + ds_put_cstr(&ctx->output, " None attached"); >> > + } >> > + ds_put_cstr(&ctx->output, "\n"); >> > + } >> > + >> > + free(mirrors); >> > +} >> > + >> > static const struct ctl_table_class tables[NBREC_N_TABLES] = { >> > [NBREC_TABLE_DHCP_OPTIONS].row_ids >> > = {{&nbrec_logical_switch_port_col_name, NULL, >> > @@ -7334,6 +7678,15 @@ static const struct ctl_command_syntax >> > nbctl_commands[] = { >> > { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list, >> > NULL, "", RO }, >> > >> > + /* mirror commands. */ >> > + { "mirror-add", 5, 5, >> > + "NAME TYPE INDEX FILTER IP", >> > + nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW }, >> > + { "mirror-del", 0, 1, "[NAME]", >> > + nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW }, >> > + { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list, >> > + NULL, "", RO }, >> > + >> > /* meter commands. */ >> > { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", >> > nbctl_pre_meter_add, >> > nbctl_meter_add, NULL, "--fair,--may-exist", RW }, >> > @@ -7388,6 +7741,10 @@ static const struct ctl_command_syntax >> > nbctl_commands[] = { >> >> > nbctl_lsp_get_dhcpv6_options, NULL, "", RO }, >> > { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls, >> > NULL, "", RO }, >> > + { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror, >> > + nbctl_lsp_attach_mirror, NULL, "", RW }, >> > + { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror, >> > + nbctl_lsp_detach_mirror, NULL, "", RW }, >> > >> > /* forwarding group commands. */ >> > { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...", >> > diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c >> > index e35556d34..1aa6cc26f 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); >> > @@ -1434,6 +1435,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 >> > >> > Thanks & Regards, > Abhiram R N _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev