On 6/26/26 9:40 AM, Dumitru Ceara wrote:
> Hi Ilya,
>
> On 6/25/26 6:45 PM, Ilya Maximets wrote:
>> On 6/23/26 2:56 PM, Dumitru Ceara wrote:
>>> On 6/23/26 1:51 PM, Lorenzo Bianconi wrote:
>>>> Filter outgoing monitor requests in the IDL layer based on the
>>>> db-schema received from the server, skipping tables and columns not
>>>> available in the schema.
>>>> To support this, store the user-provided monitor condition in pre-JSON
>>>> format in ovsdb_idl_table when ovsdb_idl_set_condition() is called,
>>>> allowing ovsdb_idl_compose_monitor_request() to build monitor
>>>> conditional requests that only include tables and columns present in
>>>> the db-schema.
>>>>
>>>> Reported-at: https://issues.redhat.com/browse/FDP-3114
>>>> Signed-off-by: Lorenzo Bianconi <[email protected]>
>>>> ---
>>>
>>> Hi Lorenzo, Ilya,
>>>
>>> I have a few minor comments down below. But I'd also be OK if this
>>> exact version gets merged. I'll leave it up to you to decide if you
>>> want to integrate my feedback or not.
>>>
>>> In any case:
>>>
>>> Acked-by: Dumitru Ceara <[email protected]>
>>>
>>> Thanks for all the work on this, it will make it a bit easier for
>>> clients (OVN) to use the IDL IMO.
>>>
>>> Regards,
>>> Dumitru
>>>
>>>> Changes in v6:
>>>> - Rebase on top of ovs main branch.
>>>> - cosmetics.
>>>> Changes in v5:
>>>> - Add self-test for last_id management in raft mode.
>>>> - Use a pointer for req_cond instead of a struct.
>>>> - Fix typos.
>>>> - Do not reset server_schema_received in ovsdb_idl_run()
>>>> - Move hmap_is_empty() after in_server_schema check in
>>>> ovsdb_idl_set_condition__().
>>>> - Add for coverage tests.
>>>> Changes in v4:
>>>> - Filter monitor requests in ovsdb_idl_set_condition__()
>>>> - Cosmetics
>>>> - Move ovsdb_cs_db_sync_condition() in ovsdb_cs_send_monitor_request() to
>>>> properly update ack_cond json struct
>>>> - Store original user monitor request in req_cond in
>>>> ovsdb_idl_set_condition()
>>>> - Add more uni-tests
>>>> - Add more comments
>>>> Changes in v3:
>>>> - Fix seqno reporting when the filtered condition is empty.
>>>> Changes in v2:
>>>> - Add missing unit-test.
>>>> - squash with patch 'ovsdb: Add ovsdb_cs_clear_condition routine to remove
>>>> stable ovsdb_cs_db_table entries.'.
>>>> - fix the error condition reported by Ilya.
>>>> - Remove unnecessary ovsdb_cs_db_sync_condition() in
>>>> ovsdb_cs_send_monitor_request().
>>>> - cosmetics.
>>>> ---
>>>> lib/ovsdb-cs.c | 50 +++++++---
>>>> lib/ovsdb-cs.h | 2 +
>>>> lib/ovsdb-idl-provider.h | 3 +
>>>> lib/ovsdb-idl.c | 111 ++++++++++++++++++++++-
>>>> tests/ovsdb-idl.at | 142 ++++++++++++++++++++++++++++-
>>>> tests/test-ovsdb.c | 191 ++++++++++++++++++++++++++++++++++++---
>>>> 6 files changed, 468 insertions(+), 31 deletions(-)
>>>>
>>>> diff --git a/lib/ovsdb-cs.c b/lib/ovsdb-cs.c
>>>> index d96a97599..aaa1611c3 100644
>>>> --- a/lib/ovsdb-cs.c
>>>> +++ b/lib/ovsdb-cs.c
>>>> @@ -412,12 +412,6 @@ ovsdb_cs_retry_at(struct ovsdb_cs *cs, const char
>>>> *where)
>>>> static void
>>>> ovsdb_cs_restart_fsm(struct ovsdb_cs *cs)
>>>> {
>>>> - /* Resync data DB table conditions to avoid missing updates due to
>>>> - * conditions that were in flight or changed locally while the
>>>> connection
>>>> - * was down.
>>>> - */
>>>> - ovsdb_cs_db_sync_condition(&cs->data);
>>>> -
>>>> ovsdb_cs_send_schema_request(cs, &cs->server);
>>>> ovsdb_cs_transition(cs, CS_S_SERVER_SCHEMA_REQUESTED);
>>>> cs->data.monitor_version = 0;
>>>> @@ -912,17 +906,47 @@ ovsdb_cs_db_get_table(struct ovsdb_cs_db *db, const
>>>> char *table)
>>>> return t;
>>>> }
>>>>
>>>> +static void
>>>> +ovsdb_cs_db_destroy_table(struct ovsdb_cs_db_table *table,
>>>> + struct ovsdb_cs_db *db)
>>>> +{
>>>> + json_destroy(table->ack_cond);
>>>> + json_destroy(table->req_cond);
>>>> + json_destroy(table->new_cond);
>>>> + hmap_remove(&db->tables, &table->hmap_node);
>>>> + free(table->name);
>>>> + free(table);
>>>> +}
>>>> +
>>>> +/* Destroy a given ovsdb_cs_db_table according to the table name. */
>>>> +void
>>>> +ovsdb_cs_clear_condition(struct ovsdb_cs *cs, const char *table)
>>>
>>> Nit: The function name reads a bit weird. The comment speaks about
>>> destroying the table, which is what the code does but the function name
>>> is about clearing the condition.
>>>
>>> Should we call this "ovsdb_cs_destroy_table()"?
>>
>> I don't think so. The cs_table construct is entirely internal to ovsdb-cs
>> module. The fact that CS module chooses to implement condition clearing
>> as destruction of the internal table is an implementation detail that IDL
>> layer should not know about, as it deals with conditions: it sets them and
>> it clears them.
>>
>> Does that make sense?
>>
>
> It's subjective, but as I said in the previous email, I can live with
> the way it is in this patch.
>
>>>
>>>> +{
>>>> + uint32_t hash = hash_string(table, 0);
>>>> + struct ovsdb_cs_db *db = &cs->data;
>>>> +
>>>> + struct ovsdb_cs_db_table *t;
>>>> + HMAP_FOR_EACH_WITH_HASH (t, hmap_node, hash, &db->tables) {
>>>> + if (!strcmp(t->name, table)) {
>>>> + ovsdb_cs_db_destroy_table(t, db);
>>>> + db->last_id = UUID_ZERO;
>>>> + return;
>>>> + }
>>>> + }
>>>> +}
>>>> +
>>>> +void
>>>> +ovsdb_cs_reset_last_id(struct ovsdb_cs *cs)
>>>> +{
>>>> + cs->data.last_id = UUID_ZERO;
>>>> +}
>>>> +
>>>> static void
>>>> ovsdb_cs_db_destroy_tables(struct ovsdb_cs_db *db)
>>>> {
>>>> struct ovsdb_cs_db_table *table;
>>>> HMAP_FOR_EACH_SAFE (table, hmap_node, &db->tables) {
>>>> - json_destroy(table->ack_cond);
>>>> - json_destroy(table->req_cond);
>>>> - json_destroy(table->new_cond);
>>>> - hmap_remove(&db->tables, &table->hmap_node);
>>>> - free(table->name);
>>>> - free(table);
>>>> + ovsdb_cs_db_destroy_table(table, db);
>>>> }
>>>> hmap_destroy(&db->tables);
>>>> }
>>>> @@ -1501,6 +1525,8 @@ ovsdb_cs_send_monitor_request(struct ovsdb_cs *cs,
>>>> struct ovsdb_cs_db *db,
>>>> /* XXX handle failure */
>>>> ovs_assert(mrs->type == JSON_OBJECT);
>>>>
>>>> + ovsdb_cs_db_sync_condition(db);
>>>> +
>>>> if (version > 1) {
>>>> struct ovsdb_cs_db_table *table;
>>>> HMAP_FOR_EACH (table, hmap_node, &db->tables) {
>>>> diff --git a/lib/ovsdb-cs.h b/lib/ovsdb-cs.h
>>>> index bcc3dcd71..98c921cd8 100644
>>>> --- a/lib/ovsdb-cs.h
>>>> +++ b/lib/ovsdb-cs.h
>>>> @@ -144,6 +144,8 @@ void ovsdb_cs_set_probe_interval(const struct ovsdb_cs
>>>> *, int probe_interval);
>>>> unsigned int ovsdb_cs_set_condition(struct ovsdb_cs *, const char *table,
>>>> const struct json *condition);
>>>> unsigned int ovsdb_cs_get_condition_seqno(const struct ovsdb_cs *);
>>>> +void ovsdb_cs_clear_condition(struct ovsdb_cs *, const char *table);
>>>> +void ovsdb_cs_reset_last_id(struct ovsdb_cs *);
>>>>
>>>> /* Database change awareness. */
>>>> void ovsdb_cs_set_db_change_aware(struct ovsdb_cs *, bool
>>>> set_db_change_aware);
>>>> diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
>>>> index 6cf32fb50..2e0ab3003 100644
>>>> --- a/lib/ovsdb-idl-provider.h
>>>> +++ b/lib/ovsdb-idl-provider.h
>>>> @@ -130,6 +130,9 @@ struct ovsdb_idl_table {
>>>> * or not. */
>>>> struct ovs_list indexes; /* Contains "struct ovsdb_idl_index"s */
>>>> struct ovs_list track_list; /* Tracked rows
>>>> (ovsdb_idl_row.track_node). */
>>>> +
>>>> + struct ovsdb_idl_condition *req_cond; /* User requested monitor
>>>> + * condition. */
>>>> };
>>>>
>>>> struct ovsdb_idl_class {
>>>> diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
>>>> index fe90deda7..8d85a23f1 100644
>>>> --- a/lib/ovsdb-idl.c
>>>> +++ b/lib/ovsdb-idl.c
>>>> @@ -99,6 +99,8 @@ struct ovsdb_idl {
>>>> struct ovs_list rows_to_reparse; /* Stores rows that might need to be
>>>> * re-parsed due to insertion of a
>>>> * referenced row. */
>>>> + bool server_schema_received; /* Set to true when the IDL has received
>>>> + * the DB schema from the server. */
>>>> };
>>>>
>>>> static struct ovsdb_cs_ops ovsdb_idl_cs_ops;
>>>> @@ -205,6 +207,17 @@ static void ovsdb_idl_add_to_indexes(const struct
>>>> ovsdb_idl_row *);
>>>> static void ovsdb_idl_remove_from_indexes(const struct ovsdb_idl_row *);
>>>> static int ovsdb_idl_try_commit_loop_txn(struct ovsdb_idl_loop *loop,
>>>> bool *may_need_wakeup);
>>>> +static void ovsdb_idl_condition_clone(struct ovsdb_idl_condition *dest,
>>>> + const struct ovsdb_idl_condition *);
>>>> +static void ovsdb_idl_create_req_condition(
>>>> + struct ovsdb_idl *,
>>>> + const struct ovsdb_idl_table_class *,
>>>> + const struct ovsdb_idl_condition *);
>>>> +static void ovsdb_idl_destroy_req_condition(struct ovsdb_idl_table *);
>>>> +static unsigned int ovsdb_idl_set_condition__(
>>>> + struct ovsdb_idl *,
>>>> + const struct ovsdb_idl_table_class *,
>>>> + const struct ovsdb_idl_condition *);
>>>>
>>>> static void add_tracked_change_for_references(struct ovsdb_idl_row *);
>>>>
>>>> @@ -266,6 +279,7 @@ ovsdb_idl_create_unconnected(const struct
>>>> ovsdb_idl_class *class,
>>>> .txn = NULL,
>>>> .outstanding_txns = HMAP_INITIALIZER(&idl->outstanding_txns),
>>>> .verify_write_only = false,
>>>> + .server_schema_received = false,
>>>> .deleted_untracked_rows
>>>> = OVS_LIST_INITIALIZER(&idl->deleted_untracked_rows),
>>>> .rows_to_reparse
>>>> @@ -298,6 +312,7 @@ ovsdb_idl_create_unconnected(const struct
>>>> ovsdb_idl_class *class,
>>>> = table->change_seqno[OVSDB_IDL_CHANGE_DELETE] = 0;
>>>> table->idl = idl;
>>>> table->in_server_schema = false;
>>>> + table->req_cond = NULL;
>>>> shash_init(&table->schema_columns);
>>>> }
>>>>
>>>> @@ -372,6 +387,7 @@ ovsdb_idl_destroy(struct ovsdb_idl *idl)
>>>>
>>>> ovsdb_idl_schema_columns_clear(&table->schema_columns);
>>>> shash_destroy(&table->schema_columns);
>>>> + ovsdb_idl_destroy_req_condition(table);
>>>>
>>>> hmap_destroy(&table->rows);
>>>> free(table->modes);
>>>> @@ -788,6 +804,7 @@ ovsdb_idl_compose_monitor_request(const struct json
>>>> *schema_json, void *idl_)
>>>> struct shash *schema = ovsdb_cs_parse_schema(schema_json);
>>>> struct json *monitor_requests = json_object_create();
>>>>
>>>> + idl->server_schema_received = true;
>>>> for (size_t i = 0; i < idl->class_->n_tables; i++) {
>>>> struct ovsdb_idl_table *table = &idl->tables[i];
>>>> const struct ovsdb_idl_table_class *tc = table->class_;
>>>> @@ -842,8 +859,12 @@ ovsdb_idl_compose_monitor_request(const struct json
>>>> *schema_json, void *idl_)
>>>> idl->class_->database, table->class_->name);
>>>> json_destroy(columns);
>>>> table->in_server_schema = false;
>>>> + ovsdb_cs_clear_condition(idl->cs, table->class_->name);
>>>> continue;
>>>> } else if (schema && table_schema) {
>>>> + if (!table->in_server_schema) {
>>>> + ovsdb_cs_reset_last_id(idl->cs);
>>>> + }
>>>
>>> I initially got confused here and thought that this would cause issues
>>> and always reset the last_id every time the IDL client reconnects to the
>>> server.
>>>
>>> But that's actually not true. This is correct because:
>>> - when the client starts up (last_id is already 0), no tables are in the
>>> server schema
>>> - then the schema is received so the tables that are actually on the
>>> server side get 'in_server_schema': false->true and last_id is "reset"
>>> (it was already 0 so that's fine)
>>> - only if we reconnect and the server has upgraded the schema (more
>>> tables are added) we reset last_id.
>>>
>>> So it's fine but it makes me wonder if we should add a tiny comment here
>>> for it.
>>
>> This is also a defensive clearing here as under normal circumstances the
>> server supposed to drop the transaction history on upgrades. But we should
>> not ask for it in the first place. We could add a comment for that.
>>
>
> Thanks!
>
>>>
>>>> table->in_server_schema = true;
>>>> }
>>>>
>>>> @@ -852,6 +873,14 @@ ovsdb_idl_compose_monitor_request(const struct json
>>>> *schema_json, void *idl_)
>>>> json_object_put(monitor_requests, tc->name,
>>>> json_array_create_1(monitor_request));
>>>> }
>>>> +
>>>> + if (!table->in_server_schema) {
>>>> + ovsdb_cs_clear_condition(idl->cs, table->class_->name);
>>>> + } else if (table->req_cond) {
>>>> + /* Update the monitor condition request according to the
>>>> + * db schema. */
>>>> + ovsdb_idl_set_condition__(idl, tc, table->req_cond);
>>>> + }
>>>> }
>>>> ovsdb_cs_free_schema(schema);
>>>>
>>>> @@ -1156,6 +1185,41 @@ ovsdb_idl_condition_add_clause__(struct
>>>> ovsdb_idl_condition *condition,
>>>> hmap_insert(&condition->clauses, &clause->hmap_node, hash);
>>>> }
>>>>
>>>> +static void
>>>> +ovsdb_idl_destroy_req_condition(struct ovsdb_idl_table *table)
>>>> +{
>>>> + ovsdb_idl_condition_destroy(table->req_cond);
>>>> + free(table->req_cond);
>>>> + table->req_cond = NULL;
>>>> +}
>>>> +
>>>> +static void
>>>> +ovsdb_idl_condition_clone(struct ovsdb_idl_condition *dest,
>>>> + const struct ovsdb_idl_condition *source)
>>>> +{
>>>> + ovsdb_idl_condition_init(dest);
>>>> +
>>>> + struct ovsdb_idl_clause *clause;
>>>> + HMAP_FOR_EACH (clause, hmap_node, &source->clauses) {
>>>> + ovsdb_idl_condition_add_clause__(dest, clause,
>>>> clause->hmap_node.hash);
>>>> + }
>>>> + dest->is_true = source->is_true;
>>>> +}
>>>> +
>>>> +static void
>>>> +ovsdb_idl_create_req_condition(struct ovsdb_idl *idl,
>>>> + const struct ovsdb_idl_table_class *tc,
>>>> + const struct ovsdb_idl_condition
>>>> *condition)
>>>> +{
>>>> + struct ovsdb_idl_table *table = shash_find_data(&idl->table_by_name,
>>>> + tc->name);
>>>> + if (table) {
>>>> + ovsdb_idl_destroy_req_condition(table);
>>>> + table->req_cond = xzalloc(sizeof *table->req_cond);
>>>> + ovsdb_idl_condition_clone(table->req_cond, condition);
>>>> + }
>>>> +}
>>>> +
>>>> /* Adds a clause to the condition for replicating the table with class
>>>> 'tc' in
>>>> * 'idl'.
>>>> *
>>>> @@ -1234,6 +1298,47 @@ ovsdb_idl_condition_to_json(const struct
>>>> ovsdb_idl_condition *cnd)
>>>> return json_array_create(clauses, n);
>>>> }
>>>>
>>>> +static unsigned int
>>>> +ovsdb_idl_set_condition__(struct ovsdb_idl *idl,
>>>> + const struct ovsdb_idl_table_class *tc,
>>>> + const struct ovsdb_idl_condition *condition)
>>>> +{
>>>> + struct ovsdb_idl_condition filter_cond =
>>>> + OVSDB_IDL_CONDITION_INIT(&filter_cond);
>>>> + struct json *cond_json;
>>>> + unsigned int seqno;
>>>> +
>>>> + if (!idl->server_schema_received) {
>>>> + goto out;
>>>> + }
>>>> +
>>>> + struct ovsdb_idl_table *t = ovsdb_idl_table_from_class(idl, tc);
>>>> + if (!t || !t->in_server_schema) {
>>>
>>> nit: We _don't_ leak anything from 'fiter_cond' here but it looks weird
>>> that we don't destroy it. I'm not sure if we should change this or not,
>>> I'm on the fence with this one. I'm not against leaving it as is though.
>>
>> We could assing seqno from the current one and goto the end to make it
>> easier to follow and let the destruction the be executed.
>>
>> Though the 'goto out' is a little confusing name here, I'd change it to
>> 'goto set_condition'. And add a new 'exit' label to jump from here.
>>
>> I can fold in something like this before applying the change if you and
>> Lorenzo agree:
>>
>> diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
>> index 8d85a23f1..9500d5327 100644
>> --- a/lib/ovsdb-idl.c
>> +++ b/lib/ovsdb-idl.c
>> @@ -863,6 +863,10 @@ ovsdb_idl_compose_monitor_request(const struct json
>> *schema_json, void *idl_)
>> continue;
>> } else if (schema && table_schema) {
>> if (!table->in_server_schema) {
>> + /* The server didn't have this table before and now it
>> + * does. It shoudl've dropped the transaction history,
Typo: shoudl -> should
>> + * but it's better if we do not ask for it in the first
>> + * place, as our conditions could be out of sync. */
>> ovsdb_cs_reset_last_id(idl->cs);
>> }
>> table->in_server_schema = true;
>> @@ -1309,18 +1313,25 @@ ovsdb_idl_set_condition__(struct ovsdb_idl *idl,
>> unsigned int seqno;
>>
>> if (!idl->server_schema_received) {
>> - goto out;
>> + /* Cant' filter yet - pass through. Will be filtered once we know
Typo: Cant' -> Can't
>> + * the server schema. */
>> + goto set_condition;
>> }
>>
>> struct ovsdb_idl_table *t = ovsdb_idl_table_from_class(idl, tc);
>> if (!t || !t->in_server_schema) {
>> - return ovsdb_idl_get_condition_seqno(idl);
>> + /* Not on the server, should not set. */
>> + seqno = ovsdb_idl_get_condition_seqno(idl);
>> + goto exit;
>> }
>>
>> if (hmap_is_empty(&condition->clauses)) {
>> - goto out;
>> + /* Trivial condition and the table is present on the server. */
>> + goto set_condition;
>> }
>>
>> + /* Non-trivial condition for a table that is in the server schema.
>> + * Filter out columns that are not. */
>> struct ovsdb_idl_clause *clause;
>> HMAP_FOR_EACH (clause, hmap_node, &condition->clauses) {
>> if (ovsdb_idl_server_has_column(idl, clause->column)) {
>> @@ -1329,13 +1340,14 @@ ovsdb_idl_set_condition__(struct ovsdb_idl *idl,
>> }
>> }
>> condition = &filter_cond;
>> -out:
>> +
>> +set_condition:
>> cond_json = ovsdb_idl_condition_to_json(condition);
>> seqno = ovsdb_cs_set_condition(idl->cs, tc->name, cond_json);
>> json_destroy(cond_json);
>>
>> +exit:
>> ovsdb_idl_condition_destroy(&filter_cond);
>> -
>> return seqno;
>> }
>>
>> ---
>>
>> WDYT?
>>
>
> Sounds good to me, thanks!
Thanks, Dumitru and Lorenzo! I folded in the diff with the typos fixed up
and applied the change.
Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev