On 5/29/25 7:39 PM, Rukomoinikova Aleksandra wrote: > Ilya, hi! thanks for the comments, I fixed the storage of filters on the > sset structure, but I have a question below > > On 29.05.2025 19:10, Ilya Maximets wrote: >> On 5/29/25 6:02 PM, Ilya Maximets wrote: >>> On 5/20/25 7:16 PM, Alexandra Rukomoinikova wrote: >>>> Added the ability to filter the output of the show command >>>> using the filter option. >>>> >>>> Signed-off-by: Alexandra Rukomoinikova <arukomoinikova@k2.cloud> >>>> --- >>>> v1 --> v2 : did as Ilya said. >>>> --- >>>> lib/db-ctl-base.c | 75 +++++++++++++++++++++++++++++++++++++++++++++- >>>> tests/ovs-vsctl.at | 28 +++++++++++++++++ >>>> 2 files changed, 102 insertions(+), 1 deletion(-) >>> Hi, Alexandra. Thanks for the new version! >>> >>> nit: The 'lib:' in the patch name doesn't make a lot of sense as vast >>> majority of OVS code is in lib. It should be 'db-ctl-base:' instead. >>> >>> Since this patch adds a new option, it needs to be documented in the >>> man page (utilities/ovs-vsctl.8.in) and a NEWS entry should be added. >>> >>> Some more comments below. >>> >>>> diff --git a/lib/db-ctl-base.c b/lib/db-ctl-base.c >>>> index 1f157e46c..45d2d51c0 100644 >>>> --- a/lib/db-ctl-base.c >>>> +++ b/lib/db-ctl-base.c >>>> @@ -2243,19 +2243,92 @@ cmd_show_row(struct ctl_context *ctx, const struct >>>> ovsdb_idl_row *row, >>>> sset_find_and_delete_assert(shown, show->table->name); >>>> } >>>> >>>> +struct output_filter { >>>> + char **filters; >>>> + size_t n_filters; >>>> +}; >>>> + >>>> +static void >>>> +filter_row(struct ctl_context *ctx, >>>> + struct output_filter *filter, >>>> + size_t old_lenght) >>> s/lenght/length/ >>> >>> here and in a few other places in the code. >>> >>>> +{ >>>> + bool filter_match = false; >>>> + >>>> + for (size_t i = 0; i < filter->n_filters; i++) { >>>> + if (strstr(&ctx->output.string[old_lenght], filter->filters[i])) { >>>> + filter_match = true; >>> break here. There is no need to continue the loop. >>> >>>> + } >>>> + } >>>> + >>>> + if (!filter_match) { >>>> + ds_truncate(&ctx->output, old_lenght); >>>> + } >>>> +} >>>> + >>>> +static struct output_filter * >>>> +init_filters(struct ctl_context *ctx) >>>> +{ >>>> + char *filter_str = shash_find_data(&ctx->options, "--filter"); >>>> + if (!filter_str || !*filter_str) { >>>> + return NULL; >>>> + } >>>> + >>>> + size_t count = 1; >>>> + for (const char *p = filter_str; *p; p++) { >>>> + if (*p == '|') { >>>> + count++; >>>> + } >>>> + } >>>> + >>>> + struct output_filter *filter = xmalloc(sizeof(struct output_filter)); >>>> + filter->filters = xmalloc(count * (sizeof(char *))); >>>> + filter->n_filters = count; >>>> + >>>> + char *ptr = NULL; >>>> + char *token = strtok_r(filter_str, "|", &ptr); >>>> + size_t idx = 0; >>>> + >>>> + while (token && idx < count) { >>>> + filter->filters[idx++] = xstrdup(token); >>>> + token = strtok_r(NULL, "|", &ptr); >>>> + } >>>> + >>>> + return filter; >>>> +} >>> If we use sset as a data structure for the filters, the code above can >>> be mostly replaced with an sset_from_delimited_string() call. >>> The sset is slightly less memory-efficient, but I don't think we care >>> in this particular case. >>> >>>> + >>>> +static void >>>> +free_filters(struct output_filter *filter) >>>> +{ >>>> + if (!filter) { >>>> + return; >>>> + } >>>> + for (size_t i = 0; i < filter->n_filters; i++) { >>>> + free(filter->filters[i]); >>>> + } >>>> + free(filter->filters); >>>> + free(filter); >>>> +} >>>> + >>>> static void >>>> cmd_show(struct ctl_context *ctx) >>>> { >>>> const struct ovsdb_idl_row *row; >>>> + struct output_filter *filter = init_filters(ctx); >>>> struct sset shown = SSET_INITIALIZER(&shown); >>>> >>>> for (row = ovsdb_idl_first_row(ctx->idl, cmd_show_tables[0].table); >>>> row; row = ovsdb_idl_next_row(row)) { >>>> + size_t lenght_before = ctx->output.length; >>>> cmd_show_row(ctx, row, 0, &shown); >>>> + if (filter) { >>>> + filter_row(ctx, filter, lenght_before); >>>> + } >>> This only covers the top-level elements, but the idea was to perform the >>> filtering recursively for every level. You should move the filtering into >>> the cmd_show_row() itself, i.e. save the length at the top of cmd_show_row() >>> and run the filtering at the bottom. This will ensure that we're filtering >>> all the way through. >>> >>> For example, if we have a setup like this: >>> >>> $ ovs-vsctl add-br br1 >>> $ ovs-vsctl add-br br0 >>> $ ovs-vsctl add-port br0 p1 >>> $ ovs-vsctl add-port br0 tunnel_port \ >>> -- set Interface tunnel_port type=geneve \ >>> options:remote_ip=flow options:key=123 >>> $ ovs-vsctl add-port br0 tunnel_port2 \ >>> -- set Interface tunnel_port2 type=geneve \ >>> options:remote_ip=flow options:key=125 >>> >>> Then I can get a list of bridges only with: >>> >>> $ ovs-vsctl --filter="Bridge" show >>> d55f21cd-a59a-4548-b1bc-fc165b0ec0a0 >>> Bridge br1 >>> Bridge br0 >>> >>> Since the 'Bridge' match happens at the higher level, all the port >>> and interface information is filtered out. >>> >>> Or a list of ports per bridge: >>> >>> $ ovs-vsctl --filter="Port" show >>> d55f21cd-a59a-4548-b1bc-fc165b0ec0a0 >>> Bridge br1 >>> Port br1 >>> Bridge br0 >>> Port p1 >>> Port tunnel_port >>> Port tunnel_port2 >>> Port br0 >>> >>> Here only the interface information is filtered out since once we match >>> on the 'Port' string, all the higher-level nodes will match as well. >>> >>> Or if I want to only show geneve interfaces: >>> >>> $ ovs-vsctl --filter="geneve" show >>> d55f21cd-a59a-4548-b1bc-fc165b0ec0a0 >>> Bridge br0 >>> Port tunnel_port >>> Interface tunnel_port >>> type: geneve >>> options: {key="123", remote_ip=flow} >>> Port tunnel_port2 >>> Interface tunnel_port2 >>> type: geneve >>> options: {key="125", remote_ip=flow} >>> >>> >>> Or if I want to show all tunnels with the tunnel key 123: >>> >>> $ ovs-vsctl --filter='key="123"' show >>> d55f21cd-a59a-4548-b1bc-fc165b0ec0a0 >>> Bridge br0 >>> Port tunnel_port >>> Interface tunnel_port >>> type: geneve >>> options: {key="123", remote_ip=flow} >>> >>> >>> Or I want the previous one, but also names of all the ports per bridge: >>> >>> $ ovs-vsctl --filter='key="123"|Port' show >>> d55f21cd-a59a-4548-b1bc-fc165b0ec0a0 >>> Bridge br1 >>> Port br1 >>> Bridge br0 >>> Port p1 >>> Port tunnel_port >>> Interface tunnel_port >>> type: geneve >>> options: {key="123", remote_ip=flow} >>> Port tunnel_port2 >>> Port br0 >>> >>> >>> What do you think? > > my idea was in many ways for the sbctl command to be able to filter by > chassis, it seems that in your approach this will not be available, and > if I filter by chassis name, I will only see its chassis name and will > not see all the information about enсap-ips and port bindings.
Hmm, good point. The recursive logic works nicely for the OVS schema, but not so much for the OVN_Southbound. So, we probably want is an option to apply filters at the level of specific tables. I spent some time thinking what would be a good syntax for this that would not limit the filtering options too much, and I came up with something like this: --filter=TABLE(FILTER[|FILTER...]),... So, for filtering on chassis names, but keeping all the encap and the port binding informations, it will look like: ovn-sbctl --filter="chassis(ch0|ch1)" show This will mean that we only apply this filter in cmd_show_row() when the current show->table is "chassis". If we'll want to only show specific port bindings in the found chassis, then we'll be able to do something like this: ovn-sbctl --filter="chassis(ch0|ch1),port-binding(sw0-p)" show This will still print all the encap-ips for all the found chassis, but will only print port bindings that contain 'sw0-p' substring. And we should still allow the simple case without specifying the table that should do the filtering on all levels, e.g. ovs-vsctl --filter='Port' show That will still only print port names inside all the bridges. And something like this: ovs-vsctl --filter='bridge(key=123)' show would print every port and interface of a bridge if at least one of the interfaces in it is a tunnel interface with a key=123. So, the solution would look very similar, but parsing will be a bit more involved... First we'll need to split by a comma, then parse each chunk by finding the first parenthesis and treating everything inside as a list of filters for the table. As a data structure to store all that, may use an shash with sset as a value, I guess. If the name of a current table is not in the shash, then use the filter that has no table specified. What do you think? >>>> } >>>> >>>> ovs_assert(sset_is_empty(&shown)); >>>> sset_destroy(&shown); >>>> + free_filters(filter); >>>> } >>>> >>>> >>>> @@ -2548,7 +2621,7 @@ ctl_init__(const struct ovsdb_idl_class *idl_class_, >>>> cmd_show_tables = cmd_show_tables_; >>>> if (cmd_show_tables) { >>>> static const struct ctl_command_syntax show = >>>> - {"show", 0, 0, "", pre_cmd_show, cmd_show, NULL, "", RO}; >>>> + {"show", 0, 0, "", pre_cmd_show, cmd_show, NULL, "--filter=", >>>> RO}; >>>> ctl_register_command(&show); >>>> } >>>> } >>>> diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at >>>> index e488e292d..010889789 100644 >>>> --- a/tests/ovs-vsctl.at >>>> +++ b/tests/ovs-vsctl.at >>>> @@ -1831,3 +1831,31 @@ AT_CHECK([ovs-vsctl --no-wait --bare --columns >>>> _uuid,name list bridge tst1], [0] >>>> >>>> OVS_VSCTL_CLEANUP >>>> AT_CLEANUP >>>> + >>>> +AT_SETUP([ovs-vsctl filter option usage]) >>> nit: I'd name it 'ovs-vsctl show filtered' or something like that. >>> >>>> +AT_KEYWORDS([ovs-vsctl filter option]) >>> nit :I'd replace the 'option' with 'show'. 'option' is a not very specific >>> keyword to use. >>> >>>> + >>>> +OVS_VSWITCHD_START([dnl >>>> + add-port br0 p1 -- set Interface p1 type=internal ofport_request=1 -- \ >>>> + add-port br0 p2 -- set Interface p2 type=internal ofport_request=2 -- \ >>>> + add-port br0 p3 -- set Interface p3 type=internal ofport_request=3 >>>> +]) >>>> + >>>> +AT_CHECK([ovs-vsctl --filter='p1' show | grep Port | sort], [0], [dnl >>>> + Port br0 >>>> + Port p1 >>>> + Port p2 >>>> + Port p3 >>>> +]) >>>> + >>>> +AT_CHECK([ovs-vsctl --filter='test' show | grep Port | sort], [0], []) >>>> + >>>> +AT_CHECK([ovs-vsctl --filter='Bridge' show | grep Port | sort], [0], [dnl >>>> + Port br0 >>>> + Port p1 >>>> + Port p2 >>>> + Port p3 >>>> +]) >>> We should print the whole thing here. It will not change from run to run, >>> because database structures are sorted and our top-level table only has one >>> row. The only thing that will change is the datapath uuid at the very top. >>> You may just cut out the first line with tail -n +2 or feed the output into >>> the uuidfilt. >> Ignore that part, we do not control the UUIDs of referenced rows, so the >> output >> order will change between runs, unfortunately. >> >>> Maybe add a bit more variety to the tests. Different interface types. >>> >>>> + >>>> +OVS_VSCTL_CLEANUP >>>> +AT_CLEANUP >>>> \ No newline at end of file >>> Add the new line here! >>> >>> Best regards, Ilya Maximets. >>> _______________________________________________ >>> dev mailing list >>> d...@openvswitch.org >>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >>> > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev