On 29.05.2025 22:21, Ilya Maximets wrote:
> 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?
I think this approach is not very user-friendly, I would honestly get 
tired of writing this in the show command=) maybe I'll make 2 flags? 
just a filter that will implement your approach, and the second 
filter-all (or filter-table, but i like filter-all more), when we can 
filter the table completely ?
>>>>>        }
>>>>>    
>>>>>        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
>>>>

-- 
regards,
Alexandra.

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to