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?
>
>> +{
>> + 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.
>
>> 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,
+ * 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
+ * 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?
Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev