]>
Commit | Line | Data |
---|---|---|
2f3ca7ea | 1 | /* Copyright (c) 2009, 2010 Nicira Networks. |
c3bb4bd7 BP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
18 | #include "ovsdb-idl.h" | |
19 | ||
20 | #include <assert.h> | |
475281c0 | 21 | #include <errno.h> |
b54e22e9 | 22 | #include <inttypes.h> |
c3bb4bd7 BP |
23 | #include <limits.h> |
24 | #include <stdlib.h> | |
25 | ||
475281c0 | 26 | #include "bitmap.h" |
d171b584 | 27 | #include "dynamic-string.h" |
b302749b | 28 | #include "fatal-signal.h" |
c3bb4bd7 BP |
29 | #include "json.h" |
30 | #include "jsonrpc.h" | |
31 | #include "ovsdb-data.h" | |
32 | #include "ovsdb-error.h" | |
33 | #include "ovsdb-idl-provider.h" | |
586bb84a | 34 | #include "poll-loop.h" |
c3bb4bd7 BP |
35 | #include "shash.h" |
36 | #include "util.h" | |
c3bb4bd7 BP |
37 | #include "vlog.h" |
38 | ||
d98e6007 | 39 | VLOG_DEFINE_THIS_MODULE(ovsdb_idl); |
5136ce49 | 40 | |
c3bb4bd7 BP |
41 | /* An arc from one idl_row to another. When row A contains a UUID that |
42 | * references row B, this is represented by an arc from A (the source) to B | |
43 | * (the destination). | |
44 | * | |
45 | * Arcs from a row to itself are omitted, that is, src and dst are always | |
46 | * different. | |
47 | * | |
48 | * Arcs are never duplicated, that is, even if there are multiple references | |
49 | * from A to B, there is only a single arc from A to B. | |
50 | * | |
51 | * Arcs are directed: an arc from A to B is the converse of an an arc from B to | |
52 | * A. Both an arc and its converse may both be present, if each row refers | |
53 | * to the other circularly. | |
54 | * | |
55 | * The source and destination row may be in the same table or in different | |
56 | * tables. | |
57 | */ | |
58 | struct ovsdb_idl_arc { | |
59 | struct list src_node; /* In src->src_arcs list. */ | |
60 | struct list dst_node; /* In dst->dst_arcs list. */ | |
61 | struct ovsdb_idl_row *src; /* Source row. */ | |
62 | struct ovsdb_idl_row *dst; /* Destination row. */ | |
63 | }; | |
64 | ||
65 | struct ovsdb_idl { | |
115f1e4d | 66 | const struct ovsdb_idl_class *class; |
c3bb4bd7 | 67 | struct jsonrpc_session *session; |
115f1e4d | 68 | struct shash table_by_name; |
ef73f86c | 69 | struct ovsdb_idl_table *tables; /* Contains "struct ovsdb_idl_table *"s.*/ |
c3bb4bd7 BP |
70 | struct json *monitor_request_id; |
71 | unsigned int last_monitor_request_seqno; | |
72 | unsigned int change_seqno; | |
475281c0 BP |
73 | |
74 | /* Transaction support. */ | |
75 | struct ovsdb_idl_txn *txn; | |
76 | struct hmap outstanding_txns; | |
77 | }; | |
78 | ||
79 | struct ovsdb_idl_txn { | |
80 | struct hmap_node hmap_node; | |
81 | struct json *request_id; | |
82 | struct ovsdb_idl *idl; | |
83 | struct hmap txn_rows; | |
84 | enum ovsdb_idl_txn_status status; | |
91e310a5 | 85 | char *error; |
577aebdf | 86 | bool dry_run; |
d171b584 | 87 | struct ds comment; |
b54e22e9 BP |
88 | |
89 | /* Increments. */ | |
90 | char *inc_table; | |
91 | char *inc_column; | |
92 | struct json *inc_where; | |
93 | unsigned int inc_index; | |
94 | int64_t inc_new_value; | |
69490970 BP |
95 | |
96 | /* Inserted rows. */ | |
97 | struct hmap inserted_rows; | |
98 | }; | |
99 | ||
100 | struct ovsdb_idl_txn_insert { | |
101 | struct hmap_node hmap_node; /* In struct ovsdb_idl_txn's inserted_rows. */ | |
102 | struct uuid dummy; /* Dummy UUID used locally. */ | |
103 | int op_index; /* Index into transaction's operation array. */ | |
104 | struct uuid real; /* Real UUID used by database server. */ | |
c3bb4bd7 BP |
105 | }; |
106 | ||
107 | static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
108 | static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
109 | ||
110 | static void ovsdb_idl_clear(struct ovsdb_idl *); | |
111 | static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *); | |
112 | static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *); | |
113 | static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *, | |
114 | const struct json *); | |
c547535a | 115 | static bool ovsdb_idl_process_update(struct ovsdb_idl_table *, |
c3bb4bd7 BP |
116 | const struct uuid *, |
117 | const struct json *old, | |
118 | const struct json *new); | |
119 | static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *); | |
120 | static void ovsdb_idl_delete_row(struct ovsdb_idl_row *); | |
c547535a | 121 | static bool ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *); |
c3bb4bd7 BP |
122 | |
123 | static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *); | |
475281c0 BP |
124 | static struct ovsdb_idl_row *ovsdb_idl_row_create__( |
125 | const struct ovsdb_idl_table_class *); | |
c3bb4bd7 BP |
126 | static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *, |
127 | const struct uuid *); | |
128 | static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *); | |
129 | ||
979821c0 BP |
130 | static void ovsdb_idl_row_parse(struct ovsdb_idl_row *); |
131 | static void ovsdb_idl_row_unparse(struct ovsdb_idl_row *); | |
475281c0 BP |
132 | static void ovsdb_idl_row_clear_old(struct ovsdb_idl_row *); |
133 | static void ovsdb_idl_row_clear_new(struct ovsdb_idl_row *); | |
134 | ||
135 | static void ovsdb_idl_txn_abort_all(struct ovsdb_idl *); | |
136 | static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *, | |
137 | const struct jsonrpc_msg *msg); | |
c3bb4bd7 | 138 | |
2ce42c88 BP |
139 | /* Creates and returns a connection to database 'remote', which should be in a |
140 | * form acceptable to jsonrpc_session_open(). The connection will maintain an | |
141 | * in-memory replica of the remote database whose schema is described by | |
142 | * 'class'. (Ordinarily 'class' is compiled from an OVSDB schema automatically | |
ef73f86c BP |
143 | * by ovsdb-idlc.) |
144 | * | |
145 | * If 'monitor_everything_by_default' is true, then everything in the remote | |
146 | * database will be replicated by default. ovsdb_idl_omit() and | |
147 | * ovsdb_idl_omit_alert() may be used to selectively drop some columns from | |
148 | * monitoring. | |
149 | * | |
150 | * If 'monitor_everything_by_default' is false, then no columns or tables will | |
151 | * be replicated by default. ovsdb_idl_add_column() and ovsdb_idl_add_table() | |
152 | * must be used to choose some columns or tables to replicate. | |
153 | */ | |
c3bb4bd7 | 154 | struct ovsdb_idl * |
ef73f86c BP |
155 | ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class, |
156 | bool monitor_everything_by_default) | |
c3bb4bd7 BP |
157 | { |
158 | struct ovsdb_idl *idl; | |
ef73f86c | 159 | uint8_t default_mode; |
c3bb4bd7 BP |
160 | size_t i; |
161 | ||
ef73f86c BP |
162 | default_mode = (monitor_everything_by_default |
163 | ? OVSDB_IDL_MONITOR | OVSDB_IDL_ALERT | |
164 | : 0); | |
165 | ||
c3bb4bd7 | 166 | idl = xzalloc(sizeof *idl); |
115f1e4d | 167 | idl->class = class; |
c3bb4bd7 | 168 | idl->session = jsonrpc_session_open(remote); |
115f1e4d BP |
169 | shash_init(&idl->table_by_name); |
170 | idl->tables = xmalloc(class->n_tables * sizeof *idl->tables); | |
c3bb4bd7 BP |
171 | for (i = 0; i < class->n_tables; i++) { |
172 | const struct ovsdb_idl_table_class *tc = &class->tables[i]; | |
115f1e4d | 173 | struct ovsdb_idl_table *table = &idl->tables[i]; |
c3bb4bd7 BP |
174 | size_t j; |
175 | ||
efdd9088 | 176 | shash_add_assert(&idl->table_by_name, tc->name, table); |
c3bb4bd7 | 177 | table->class = tc; |
c547535a | 178 | table->modes = xmalloc(tc->n_columns); |
ef73f86c BP |
179 | memset(table->modes, default_mode, tc->n_columns); |
180 | table->need_table = false; | |
c3bb4bd7 BP |
181 | shash_init(&table->columns); |
182 | for (j = 0; j < tc->n_columns; j++) { | |
183 | const struct ovsdb_idl_column *column = &tc->columns[j]; | |
184 | ||
efdd9088 | 185 | shash_add_assert(&table->columns, column->name, column); |
c3bb4bd7 BP |
186 | } |
187 | hmap_init(&table->rows); | |
188 | table->idl = idl; | |
189 | } | |
190 | idl->last_monitor_request_seqno = UINT_MAX; | |
475281c0 | 191 | hmap_init(&idl->outstanding_txns); |
c3bb4bd7 BP |
192 | |
193 | return idl; | |
194 | } | |
195 | ||
2ce42c88 | 196 | /* Destroys 'idl' and all of the data structures that it manages. */ |
c3bb4bd7 BP |
197 | void |
198 | ovsdb_idl_destroy(struct ovsdb_idl *idl) | |
199 | { | |
200 | if (idl) { | |
115f1e4d | 201 | size_t i; |
c3bb4bd7 | 202 | |
475281c0 | 203 | assert(!idl->txn); |
c3bb4bd7 BP |
204 | ovsdb_idl_clear(idl); |
205 | jsonrpc_session_close(idl->session); | |
206 | ||
115f1e4d BP |
207 | for (i = 0; i < idl->class->n_tables; i++) { |
208 | struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
209 | shash_destroy(&table->columns); |
210 | hmap_destroy(&table->rows); | |
c547535a | 211 | free(table->modes); |
c3bb4bd7 | 212 | } |
115f1e4d BP |
213 | shash_destroy(&idl->table_by_name); |
214 | free(idl->tables); | |
c3bb4bd7 BP |
215 | json_destroy(idl->monitor_request_id); |
216 | free(idl); | |
217 | } | |
218 | } | |
219 | ||
220 | static void | |
221 | ovsdb_idl_clear(struct ovsdb_idl *idl) | |
222 | { | |
c3bb4bd7 | 223 | bool changed = false; |
115f1e4d | 224 | size_t i; |
c3bb4bd7 | 225 | |
115f1e4d BP |
226 | for (i = 0; i < idl->class->n_tables; i++) { |
227 | struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
228 | struct ovsdb_idl_row *row, *next_row; |
229 | ||
230 | if (hmap_is_empty(&table->rows)) { | |
231 | continue; | |
232 | } | |
233 | ||
234 | changed = true; | |
4e8e4213 | 235 | HMAP_FOR_EACH_SAFE (row, next_row, hmap_node, &table->rows) { |
c3bb4bd7 BP |
236 | struct ovsdb_idl_arc *arc, *next_arc; |
237 | ||
238 | if (!ovsdb_idl_row_is_orphan(row)) { | |
979821c0 | 239 | ovsdb_idl_row_unparse(row); |
c3bb4bd7 | 240 | } |
4e8e4213 | 241 | LIST_FOR_EACH_SAFE (arc, next_arc, src_node, &row->src_arcs) { |
c3bb4bd7 BP |
242 | free(arc); |
243 | } | |
244 | /* No need to do anything with dst_arcs: some node has those arcs | |
245 | * as forward arcs and will destroy them itself. */ | |
246 | ||
0196cc15 | 247 | ovsdb_idl_row_destroy(row); |
c3bb4bd7 BP |
248 | } |
249 | } | |
250 | ||
251 | if (changed) { | |
252 | idl->change_seqno++; | |
253 | } | |
254 | } | |
255 | ||
2ce42c88 BP |
256 | /* Processes a batch of messages from the database server on 'idl'. Returns |
257 | * true if the database as seen through 'idl' changed, false if it did not | |
258 | * change. The initial fetch of the entire contents of the remote database is | |
259 | * considered to be one kind of change. | |
260 | * | |
261 | * When this function returns false, the client may continue to use any data | |
262 | * structures it obtained from 'idl' in the past. But when it returns true, | |
263 | * the client must not access any of these data structures again, because they | |
264 | * could have freed or reused for other purposes. | |
265 | * | |
266 | * This function can return occasional false positives, that is, report that | |
267 | * the database changed even though it didn't. This happens if the connection | |
268 | * to the database drops and reconnects, which causes the database contents to | |
269 | * be reloaded even if they didn't change. (It could also happen if the | |
270 | * database server sends out a "change" that reflects what we already thought | |
271 | * was in the database, but the database server is not supposed to do that.) | |
272 | * | |
273 | * As an alternative to checking the return value, the client may check for | |
274 | * changes in the value returned by ovsdb_idl_get_seqno(). | |
275 | */ | |
4ea21243 | 276 | bool |
c3bb4bd7 BP |
277 | ovsdb_idl_run(struct ovsdb_idl *idl) |
278 | { | |
4ea21243 | 279 | unsigned int initial_change_seqno = idl->change_seqno; |
c3bb4bd7 BP |
280 | int i; |
281 | ||
8bc915de | 282 | assert(!idl->txn); |
c3bb4bd7 BP |
283 | jsonrpc_session_run(idl->session); |
284 | for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) { | |
285 | struct jsonrpc_msg *msg, *reply; | |
286 | unsigned int seqno; | |
287 | ||
288 | seqno = jsonrpc_session_get_seqno(idl->session); | |
289 | if (idl->last_monitor_request_seqno != seqno) { | |
290 | idl->last_monitor_request_seqno = seqno; | |
475281c0 | 291 | ovsdb_idl_txn_abort_all(idl); |
c3bb4bd7 BP |
292 | ovsdb_idl_send_monitor_request(idl); |
293 | break; | |
294 | } | |
295 | ||
296 | msg = jsonrpc_session_recv(idl->session); | |
297 | if (!msg) { | |
298 | break; | |
299 | } | |
300 | ||
301 | reply = NULL; | |
4931f33a | 302 | if (msg->type == JSONRPC_NOTIFY |
c3bb4bd7 BP |
303 | && !strcmp(msg->method, "update") |
304 | && msg->params->type == JSON_ARRAY | |
305 | && msg->params->u.array.n == 2 | |
306 | && msg->params->u.array.elems[0]->type == JSON_NULL) { | |
307 | ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]); | |
308 | } else if (msg->type == JSONRPC_REPLY | |
309 | && idl->monitor_request_id | |
310 | && json_equal(idl->monitor_request_id, msg->id)) { | |
c547535a | 311 | idl->change_seqno++; |
c3bb4bd7 BP |
312 | json_destroy(idl->monitor_request_id); |
313 | idl->monitor_request_id = NULL; | |
314 | ovsdb_idl_clear(idl); | |
315 | ovsdb_idl_parse_update(idl, msg->result); | |
316 | } else if (msg->type == JSONRPC_REPLY | |
317 | && msg->id && msg->id->type == JSON_STRING | |
318 | && !strcmp(msg->id->u.string, "echo")) { | |
319 | /* It's a reply to our echo request. Ignore it. */ | |
475281c0 BP |
320 | } else if ((msg->type == JSONRPC_ERROR |
321 | || msg->type == JSONRPC_REPLY) | |
322 | && ovsdb_idl_txn_process_reply(idl, msg)) { | |
323 | /* ovsdb_idl_txn_process_reply() did everything needful. */ | |
c3bb4bd7 | 324 | } else { |
c5a80c70 BP |
325 | /* This can happen if ovsdb_idl_txn_destroy() is called to destroy |
326 | * a transaction before we receive the reply, so keep the log level | |
327 | * low. */ | |
328 | VLOG_DBG("%s: received unexpected %s message", | |
329 | jsonrpc_session_get_name(idl->session), | |
330 | jsonrpc_msg_type_to_string(msg->type)); | |
c3bb4bd7 BP |
331 | } |
332 | if (reply) { | |
333 | jsonrpc_session_send(idl->session, reply); | |
334 | } | |
335 | jsonrpc_msg_destroy(msg); | |
336 | } | |
4ea21243 BP |
337 | |
338 | return initial_change_seqno != idl->change_seqno; | |
c3bb4bd7 BP |
339 | } |
340 | ||
2ce42c88 BP |
341 | /* Arranges for poll_block() to wake up when ovsdb_idl_run() has something to |
342 | * do or when activity occurs on a transaction on 'idl'. */ | |
c3bb4bd7 BP |
343 | void |
344 | ovsdb_idl_wait(struct ovsdb_idl *idl) | |
345 | { | |
346 | jsonrpc_session_wait(idl->session); | |
347 | jsonrpc_session_recv_wait(idl->session); | |
348 | } | |
349 | ||
2ce42c88 BP |
350 | /* Returns a number that represents the state of 'idl'. When 'idl' is updated |
351 | * (by ovsdb_idl_run()), the return value changes. */ | |
c3bb4bd7 BP |
352 | unsigned int |
353 | ovsdb_idl_get_seqno(const struct ovsdb_idl *idl) | |
354 | { | |
355 | return idl->change_seqno; | |
356 | } | |
357 | ||
2ce42c88 BP |
358 | /* Returns true if 'idl' successfully connected to the remote database and |
359 | * retrieved its contents (even if the connection subsequently dropped and is | |
360 | * in the process of reconnecting). If so, then 'idl' contains an atomic | |
361 | * snapshot of the database's contents (but it might be arbitrarily old if the | |
362 | * connection dropped). | |
363 | * | |
364 | * Returns false if 'idl' has never connected or retrieved the database's | |
365 | * contents. If so, 'idl' is empty. */ | |
f3d64521 JP |
366 | bool |
367 | ovsdb_idl_has_ever_connected(const struct ovsdb_idl *idl) | |
368 | { | |
369 | return ovsdb_idl_get_seqno(idl) != 0; | |
370 | } | |
371 | ||
2ce42c88 BP |
372 | /* Forces 'idl' to drop its connection to the database and reconnect. In the |
373 | * meantime, the contents of 'idl' will not change. */ | |
c3bb4bd7 BP |
374 | void |
375 | ovsdb_idl_force_reconnect(struct ovsdb_idl *idl) | |
376 | { | |
377 | jsonrpc_session_force_reconnect(idl->session); | |
378 | } | |
ef73f86c BP |
379 | \f |
380 | static unsigned char * | |
381 | ovsdb_idl_get_mode(struct ovsdb_idl *idl, | |
382 | const struct ovsdb_idl_column *column) | |
c547535a BP |
383 | { |
384 | size_t i; | |
385 | ||
ef73f86c BP |
386 | assert(!idl->change_seqno); |
387 | ||
c547535a BP |
388 | for (i = 0; i < idl->class->n_tables; i++) { |
389 | const struct ovsdb_idl_table *table = &idl->tables[i]; | |
390 | const struct ovsdb_idl_table_class *tc = table->class; | |
391 | ||
392 | if (column >= tc->columns && column < &tc->columns[tc->n_columns]) { | |
ef73f86c | 393 | return &table->modes[column - tc->columns]; |
c547535a BP |
394 | } |
395 | } | |
396 | ||
397 | NOT_REACHED(); | |
398 | } | |
399 | ||
ef73f86c BP |
400 | static void |
401 | add_ref_table(struct ovsdb_idl *idl, const struct ovsdb_base_type *base) | |
402 | { | |
403 | if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) { | |
404 | struct ovsdb_idl_table *table; | |
405 | ||
406 | table = shash_find_data(&idl->table_by_name, | |
407 | base->u.uuid.refTableName); | |
408 | if (table) { | |
409 | table->need_table = true; | |
410 | } else { | |
411 | VLOG_WARN("%s IDL class missing referenced table %s", | |
412 | idl->class->database, base->u.uuid.refTableName); | |
413 | } | |
414 | } | |
415 | } | |
416 | ||
417 | /* Turns on OVSDB_IDL_MONITOR and OVSDB_IDL_ALERT for 'column' in 'idl'. Also | |
418 | * ensures that any tables referenced by 'column' will be replicated, even if | |
419 | * no columns in that table are selected for replication (see | |
420 | * ovsdb_idl_add_table() for more information). | |
c547535a | 421 | * |
ef73f86c BP |
422 | * This function is only useful if 'monitor_everything_by_default' was false in |
423 | * the call to ovsdb_idl_create(). This function should be called between | |
424 | * ovsdb_idl_create() and the first call to ovsdb_idl_run(). | |
425 | */ | |
426 | void | |
427 | ovsdb_idl_add_column(struct ovsdb_idl *idl, | |
428 | const struct ovsdb_idl_column *column) | |
429 | { | |
430 | *ovsdb_idl_get_mode(idl, column) = OVSDB_IDL_MONITOR | OVSDB_IDL_ALERT; | |
431 | add_ref_table(idl, &column->type.key); | |
432 | add_ref_table(idl, &column->type.value); | |
433 | } | |
434 | ||
435 | /* Ensures that the table with class 'tc' will be replicated on 'idl' even if | |
436 | * no columns are selected for replication. This can be useful because it | |
437 | * allows 'idl' to keep track of what rows in the table actually exist, which | |
438 | * in turn allows columns that reference the table to have accurate contents. | |
439 | * (The IDL presents the database with references to rows that do not exist | |
440 | * removed.) | |
c547535a | 441 | * |
ef73f86c BP |
442 | * This function is only useful if 'monitor_everything_by_default' was false in |
443 | * the call to ovsdb_idl_create(). This function should be called between | |
444 | * ovsdb_idl_create() and the first call to ovsdb_idl_run(). | |
445 | */ | |
446 | void | |
447 | ovsdb_idl_add_table(struct ovsdb_idl *idl, | |
448 | const struct ovsdb_idl_table_class *tc) | |
449 | { | |
450 | size_t i; | |
451 | ||
452 | for (i = 0; i < idl->class->n_tables; i++) { | |
453 | struct ovsdb_idl_table *table = &idl->tables[i]; | |
454 | ||
455 | if (table->class == tc) { | |
456 | table->need_table = true; | |
457 | return; | |
458 | } | |
459 | } | |
460 | ||
461 | NOT_REACHED(); | |
462 | } | |
463 | ||
464 | /* Turns off OVSDB_IDL_ALERT for 'column' in 'idl'. | |
c547535a | 465 | * |
ef73f86c BP |
466 | * This function should be called between ovsdb_idl_create() and the first call |
467 | * to ovsdb_idl_run(). | |
468 | */ | |
c547535a | 469 | void |
ef73f86c BP |
470 | ovsdb_idl_omit_alert(struct ovsdb_idl *idl, |
471 | const struct ovsdb_idl_column *column) | |
c547535a | 472 | { |
ef73f86c | 473 | *ovsdb_idl_get_mode(idl, column) &= ~OVSDB_IDL_ALERT; |
c547535a BP |
474 | } |
475 | ||
ef73f86c BP |
476 | /* Sets the mode for 'column' in 'idl' to 0. See the big comment above |
477 | * OVSDB_IDL_MONITOR for details. | |
c547535a | 478 | * |
ef73f86c BP |
479 | * This function should be called between ovsdb_idl_create() and the first call |
480 | * to ovsdb_idl_run(). | |
481 | */ | |
c547535a BP |
482 | void |
483 | ovsdb_idl_omit(struct ovsdb_idl *idl, const struct ovsdb_idl_column *column) | |
484 | { | |
ef73f86c | 485 | *ovsdb_idl_get_mode(idl, column) = 0; |
c547535a | 486 | } |
c3bb4bd7 BP |
487 | \f |
488 | static void | |
489 | ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl) | |
490 | { | |
491 | struct json *monitor_requests; | |
c3bb4bd7 | 492 | struct jsonrpc_msg *msg; |
115f1e4d | 493 | size_t i; |
c3bb4bd7 BP |
494 | |
495 | monitor_requests = json_object_create(); | |
115f1e4d BP |
496 | for (i = 0; i < idl->class->n_tables; i++) { |
497 | const struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
498 | const struct ovsdb_idl_table_class *tc = table->class; |
499 | struct json *monitor_request, *columns; | |
2a022368 | 500 | size_t j; |
c3bb4bd7 | 501 | |
ef73f86c | 502 | columns = table->need_table ? json_array_create_empty() : NULL; |
2a022368 BP |
503 | for (j = 0; j < tc->n_columns; j++) { |
504 | const struct ovsdb_idl_column *column = &tc->columns[j]; | |
ef73f86c BP |
505 | if (table->modes[j] & OVSDB_IDL_MONITOR) { |
506 | if (!columns) { | |
507 | columns = json_array_create_empty(); | |
508 | } | |
c547535a BP |
509 | json_array_add(columns, json_string_create(column->name)); |
510 | } | |
c3bb4bd7 | 511 | } |
ef73f86c BP |
512 | |
513 | if (columns) { | |
514 | monitor_request = json_object_create(); | |
515 | json_object_put(monitor_request, "columns", columns); | |
516 | json_object_put(monitor_requests, tc->name, monitor_request); | |
517 | } | |
c3bb4bd7 BP |
518 | } |
519 | ||
520 | json_destroy(idl->monitor_request_id); | |
521 | msg = jsonrpc_create_request( | |
9cb53f26 BP |
522 | "monitor", |
523 | json_array_create_3(json_string_create(idl->class->database), | |
524 | json_null_create(), monitor_requests), | |
c3bb4bd7 BP |
525 | &idl->monitor_request_id); |
526 | jsonrpc_session_send(idl->session, msg); | |
527 | } | |
528 | ||
529 | static void | |
530 | ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates) | |
531 | { | |
c547535a | 532 | struct ovsdb_error *error = ovsdb_idl_parse_update__(idl, table_updates); |
c3bb4bd7 BP |
533 | if (error) { |
534 | if (!VLOG_DROP_WARN(&syntax_rl)) { | |
535 | char *s = ovsdb_error_to_string(error); | |
536 | VLOG_WARN_RL(&syntax_rl, "%s", s); | |
537 | free(s); | |
538 | } | |
539 | ovsdb_error_destroy(error); | |
540 | } | |
541 | } | |
542 | ||
543 | static struct ovsdb_error * | |
544 | ovsdb_idl_parse_update__(struct ovsdb_idl *idl, | |
545 | const struct json *table_updates) | |
546 | { | |
547 | const struct shash_node *tables_node; | |
548 | ||
549 | if (table_updates->type != JSON_OBJECT) { | |
550 | return ovsdb_syntax_error(table_updates, NULL, | |
551 | "<table-updates> is not an object"); | |
552 | } | |
553 | SHASH_FOR_EACH (tables_node, json_object(table_updates)) { | |
554 | const struct json *table_update = tables_node->data; | |
555 | const struct shash_node *table_node; | |
556 | struct ovsdb_idl_table *table; | |
557 | ||
115f1e4d | 558 | table = shash_find_data(&idl->table_by_name, tables_node->name); |
c3bb4bd7 BP |
559 | if (!table) { |
560 | return ovsdb_syntax_error( | |
561 | table_updates, NULL, | |
562 | "<table-updates> includes unknown table \"%s\"", | |
563 | tables_node->name); | |
564 | } | |
565 | ||
566 | if (table_update->type != JSON_OBJECT) { | |
567 | return ovsdb_syntax_error(table_update, NULL, | |
568 | "<table-update> for table \"%s\" is " | |
569 | "not an object", table->class->name); | |
570 | } | |
571 | SHASH_FOR_EACH (table_node, json_object(table_update)) { | |
572 | const struct json *row_update = table_node->data; | |
573 | const struct json *old_json, *new_json; | |
574 | struct uuid uuid; | |
575 | ||
576 | if (!uuid_from_string(&uuid, table_node->name)) { | |
577 | return ovsdb_syntax_error(table_update, NULL, | |
578 | "<table-update> for table \"%s\" " | |
579 | "contains bad UUID " | |
580 | "\"%s\" as member name", | |
581 | table->class->name, | |
582 | table_node->name); | |
583 | } | |
584 | if (row_update->type != JSON_OBJECT) { | |
585 | return ovsdb_syntax_error(row_update, NULL, | |
586 | "<table-update> for table \"%s\" " | |
587 | "contains <row-update> for %s that " | |
588 | "is not an object", | |
589 | table->class->name, | |
590 | table_node->name); | |
591 | } | |
592 | ||
593 | old_json = shash_find_data(json_object(row_update), "old"); | |
594 | new_json = shash_find_data(json_object(row_update), "new"); | |
595 | if (old_json && old_json->type != JSON_OBJECT) { | |
596 | return ovsdb_syntax_error(old_json, NULL, | |
597 | "\"old\" <row> is not object"); | |
598 | } else if (new_json && new_json->type != JSON_OBJECT) { | |
599 | return ovsdb_syntax_error(new_json, NULL, | |
600 | "\"new\" <row> is not object"); | |
601 | } else if ((old_json != NULL) + (new_json != NULL) | |
602 | != shash_count(json_object(row_update))) { | |
603 | return ovsdb_syntax_error(row_update, NULL, | |
604 | "<row-update> contains unexpected " | |
605 | "member"); | |
606 | } else if (!old_json && !new_json) { | |
607 | return ovsdb_syntax_error(row_update, NULL, | |
608 | "<row-update> missing \"old\" " | |
609 | "and \"new\" members"); | |
610 | } | |
611 | ||
c547535a BP |
612 | if (ovsdb_idl_process_update(table, &uuid, old_json, new_json)) { |
613 | idl->change_seqno++; | |
614 | } | |
c3bb4bd7 BP |
615 | } |
616 | } | |
617 | ||
618 | return NULL; | |
619 | } | |
620 | ||
621 | static struct ovsdb_idl_row * | |
622 | ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid) | |
623 | { | |
624 | struct ovsdb_idl_row *row; | |
625 | ||
4e8e4213 | 626 | HMAP_FOR_EACH_WITH_HASH (row, hmap_node, uuid_hash(uuid), &table->rows) { |
c3bb4bd7 BP |
627 | if (uuid_equals(&row->uuid, uuid)) { |
628 | return row; | |
629 | } | |
630 | } | |
631 | return NULL; | |
632 | } | |
633 | ||
c547535a BP |
634 | /* Returns true if a column with mode OVSDB_IDL_MODE_RW changed, false |
635 | * otherwise. */ | |
636 | static bool | |
c3bb4bd7 BP |
637 | ovsdb_idl_process_update(struct ovsdb_idl_table *table, |
638 | const struct uuid *uuid, const struct json *old, | |
639 | const struct json *new) | |
640 | { | |
641 | struct ovsdb_idl_row *row; | |
642 | ||
643 | row = ovsdb_idl_get_row(table, uuid); | |
644 | if (!new) { | |
645 | /* Delete row. */ | |
646 | if (row && !ovsdb_idl_row_is_orphan(row)) { | |
647 | /* XXX perhaps we should check the 'old' values? */ | |
648 | ovsdb_idl_delete_row(row); | |
649 | } else { | |
650 | VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" " | |
651 | "from table %s", | |
652 | UUID_ARGS(uuid), table->class->name); | |
c547535a | 653 | return false; |
c3bb4bd7 BP |
654 | } |
655 | } else if (!old) { | |
656 | /* Insert row. */ | |
657 | if (!row) { | |
658 | ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); | |
659 | } else if (ovsdb_idl_row_is_orphan(row)) { | |
660 | ovsdb_idl_insert_row(row, new); | |
661 | } else { | |
662 | VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to " | |
663 | "table %s", UUID_ARGS(uuid), table->class->name); | |
c547535a | 664 | return ovsdb_idl_modify_row(row, new); |
c3bb4bd7 BP |
665 | } |
666 | } else { | |
667 | /* Modify row. */ | |
668 | if (row) { | |
669 | /* XXX perhaps we should check the 'old' values? */ | |
670 | if (!ovsdb_idl_row_is_orphan(row)) { | |
c547535a | 671 | return ovsdb_idl_modify_row(row, new); |
c3bb4bd7 BP |
672 | } else { |
673 | VLOG_WARN_RL(&semantic_rl, "cannot modify missing but " | |
674 | "referenced row "UUID_FMT" in table %s", | |
675 | UUID_ARGS(uuid), table->class->name); | |
676 | ovsdb_idl_insert_row(row, new); | |
677 | } | |
678 | } else { | |
679 | VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" " | |
680 | "in table %s", UUID_ARGS(uuid), table->class->name); | |
681 | ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); | |
682 | } | |
683 | } | |
c547535a BP |
684 | |
685 | return true; | |
c3bb4bd7 BP |
686 | } |
687 | ||
c547535a BP |
688 | /* Returns true if a column with mode OVSDB_IDL_MODE_RW changed, false |
689 | * otherwise. */ | |
690 | static bool | |
c3bb4bd7 BP |
691 | ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json) |
692 | { | |
693 | struct ovsdb_idl_table *table = row->table; | |
694 | struct shash_node *node; | |
c547535a | 695 | bool changed = false; |
c3bb4bd7 BP |
696 | |
697 | SHASH_FOR_EACH (node, json_object(row_json)) { | |
698 | const char *column_name = node->name; | |
699 | const struct ovsdb_idl_column *column; | |
700 | struct ovsdb_datum datum; | |
701 | struct ovsdb_error *error; | |
702 | ||
703 | column = shash_find_data(&table->columns, column_name); | |
704 | if (!column) { | |
705 | VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT, | |
706 | column_name, UUID_ARGS(&row->uuid)); | |
707 | continue; | |
708 | } | |
709 | ||
710 | error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL); | |
711 | if (!error) { | |
c547535a | 712 | unsigned int column_idx = column - table->class->columns; |
e85bbd75 BP |
713 | struct ovsdb_datum *old = &row->old[column_idx]; |
714 | ||
715 | if (!ovsdb_datum_equals(old, &datum, &column->type)) { | |
716 | ovsdb_datum_swap(old, &datum); | |
ef73f86c | 717 | if (table->modes[column_idx] & OVSDB_IDL_ALERT) { |
e85bbd75 BP |
718 | changed = true; |
719 | } | |
720 | } else { | |
721 | /* Didn't really change but the OVSDB monitor protocol always | |
722 | * includes every value in a row. */ | |
c547535a | 723 | } |
e85bbd75 BP |
724 | |
725 | ovsdb_datum_destroy(&datum, &column->type); | |
c3bb4bd7 BP |
726 | } else { |
727 | char *s = ovsdb_error_to_string(error); | |
728 | VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT | |
729 | " in table %s: %s", column_name, | |
730 | UUID_ARGS(&row->uuid), table->class->name, s); | |
731 | free(s); | |
732 | ovsdb_error_destroy(error); | |
733 | } | |
734 | } | |
c547535a | 735 | return changed; |
c3bb4bd7 BP |
736 | } |
737 | ||
90887925 BP |
738 | /* When a row A refers to row B through a column with a "refTable" constraint, |
739 | * but row B does not exist, row B is called an "orphan row". Orphan rows | |
740 | * should not persist, because the database enforces referential integrity, but | |
741 | * they can appear transiently as changes from the database are received (the | |
742 | * database doesn't try to topologically sort them and circular references mean | |
743 | * it isn't always possible anyhow). | |
744 | * | |
745 | * This function returns true if 'row' is an orphan row, otherwise false. | |
746 | */ | |
c3bb4bd7 BP |
747 | static bool |
748 | ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row) | |
749 | { | |
90887925 BP |
750 | return !row->old && !row->new; |
751 | } | |
752 | ||
f2ba3c0a BP |
753 | /* Returns true if 'row' is conceptually part of the database as modified by |
754 | * the current transaction (if any), false otherwise. | |
755 | * | |
756 | * This function will return true if 'row' is not an orphan (see the comment on | |
757 | * ovsdb_idl_row_is_orphan()) and: | |
758 | * | |
759 | * - 'row' exists in the database and has not been deleted within the | |
760 | * current transaction (if any). | |
761 | * | |
762 | * - 'row' was inserted within the current transaction and has not been | |
763 | * deleted. (In the latter case you should not have passed 'row' in at | |
764 | * all, because ovsdb_idl_txn_delete() freed it.) | |
765 | * | |
766 | * This function will return false if 'row' is an orphan or if 'row' was | |
767 | * deleted within the current transaction. | |
768 | */ | |
769 | static bool | |
770 | ovsdb_idl_row_exists(const struct ovsdb_idl_row *row) | |
771 | { | |
772 | return row->new != NULL; | |
c3bb4bd7 BP |
773 | } |
774 | ||
979821c0 BP |
775 | static void |
776 | ovsdb_idl_row_parse(struct ovsdb_idl_row *row) | |
777 | { | |
778 | const struct ovsdb_idl_table_class *class = row->table->class; | |
779 | size_t i; | |
780 | ||
781 | for (i = 0; i < class->n_columns; i++) { | |
782 | const struct ovsdb_idl_column *c = &class->columns[i]; | |
783 | (c->parse)(row, &row->old[i]); | |
784 | } | |
785 | } | |
786 | ||
787 | static void | |
788 | ovsdb_idl_row_unparse(struct ovsdb_idl_row *row) | |
789 | { | |
790 | const struct ovsdb_idl_table_class *class = row->table->class; | |
791 | size_t i; | |
792 | ||
793 | for (i = 0; i < class->n_columns; i++) { | |
794 | const struct ovsdb_idl_column *c = &class->columns[i]; | |
795 | (c->unparse)(row); | |
796 | } | |
797 | } | |
798 | ||
c3bb4bd7 | 799 | static void |
475281c0 | 800 | ovsdb_idl_row_clear_old(struct ovsdb_idl_row *row) |
c3bb4bd7 | 801 | { |
475281c0 | 802 | assert(row->old == row->new); |
c3bb4bd7 BP |
803 | if (!ovsdb_idl_row_is_orphan(row)) { |
804 | const struct ovsdb_idl_table_class *class = row->table->class; | |
805 | size_t i; | |
806 | ||
807 | for (i = 0; i < class->n_columns; i++) { | |
475281c0 BP |
808 | ovsdb_datum_destroy(&row->old[i], &class->columns[i].type); |
809 | } | |
810 | free(row->old); | |
811 | row->old = row->new = NULL; | |
812 | } | |
813 | } | |
814 | ||
815 | static void | |
816 | ovsdb_idl_row_clear_new(struct ovsdb_idl_row *row) | |
817 | { | |
818 | if (row->old != row->new) { | |
819 | if (row->new) { | |
820 | const struct ovsdb_idl_table_class *class = row->table->class; | |
821 | size_t i; | |
822 | ||
35c2ce98 JG |
823 | if (row->written) { |
824 | BITMAP_FOR_EACH_1 (i, class->n_columns, row->written) { | |
825 | ovsdb_datum_destroy(&row->new[i], &class->columns[i].type); | |
826 | } | |
475281c0 BP |
827 | } |
828 | free(row->new); | |
829 | free(row->written); | |
830 | row->written = NULL; | |
c3bb4bd7 | 831 | } |
475281c0 | 832 | row->new = row->old; |
c3bb4bd7 BP |
833 | } |
834 | } | |
835 | ||
836 | static void | |
837 | ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts) | |
838 | { | |
839 | struct ovsdb_idl_arc *arc, *next; | |
840 | ||
841 | /* Delete all forward arcs. If 'destroy_dsts', destroy any orphaned rows | |
842 | * that this causes to be unreferenced. */ | |
4e8e4213 | 843 | LIST_FOR_EACH_SAFE (arc, next, src_node, &row->src_arcs) { |
c3bb4bd7 BP |
844 | list_remove(&arc->dst_node); |
845 | if (destroy_dsts | |
846 | && ovsdb_idl_row_is_orphan(arc->dst) | |
847 | && list_is_empty(&arc->dst->dst_arcs)) { | |
848 | ovsdb_idl_row_destroy(arc->dst); | |
849 | } | |
850 | free(arc); | |
851 | } | |
852 | list_init(&row->src_arcs); | |
853 | } | |
854 | ||
855 | /* Force nodes that reference 'row' to reparse. */ | |
856 | static void | |
0196cc15 | 857 | ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row) |
c3bb4bd7 BP |
858 | { |
859 | struct ovsdb_idl_arc *arc, *next; | |
860 | ||
861 | /* This is trickier than it looks. ovsdb_idl_row_clear_arcs() will destroy | |
862 | * 'arc', so we need to use the "safe" variant of list traversal. However, | |
979821c0 BP |
863 | * calling an ovsdb_idl_column's 'parse' function will add an arc |
864 | * equivalent to 'arc' to row->arcs. That could be a problem for | |
865 | * traversal, but it adds it at the beginning of the list to prevent us | |
866 | * from stumbling upon it again. | |
c3bb4bd7 BP |
867 | * |
868 | * (If duplicate arcs were possible then we would need to make sure that | |
869 | * 'next' didn't also point into 'arc''s destination, but we forbid | |
870 | * duplicate arcs.) */ | |
4e8e4213 | 871 | LIST_FOR_EACH_SAFE (arc, next, dst_node, &row->dst_arcs) { |
c3bb4bd7 BP |
872 | struct ovsdb_idl_row *ref = arc->src; |
873 | ||
979821c0 | 874 | ovsdb_idl_row_unparse(ref); |
0196cc15 | 875 | ovsdb_idl_row_clear_arcs(ref, false); |
979821c0 | 876 | ovsdb_idl_row_parse(ref); |
c3bb4bd7 BP |
877 | } |
878 | } | |
879 | ||
475281c0 BP |
880 | static struct ovsdb_idl_row * |
881 | ovsdb_idl_row_create__(const struct ovsdb_idl_table_class *class) | |
882 | { | |
72c6edc5 | 883 | struct ovsdb_idl_row *row = xzalloc(class->allocation_size); |
475281c0 BP |
884 | list_init(&row->src_arcs); |
885 | list_init(&row->dst_arcs); | |
886 | hmap_node_nullify(&row->txn_node); | |
887 | return row; | |
888 | } | |
889 | ||
c3bb4bd7 BP |
890 | static struct ovsdb_idl_row * |
891 | ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid) | |
892 | { | |
475281c0 | 893 | struct ovsdb_idl_row *row = ovsdb_idl_row_create__(table->class); |
c3bb4bd7 BP |
894 | hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid)); |
895 | row->uuid = *uuid; | |
c3bb4bd7 | 896 | row->table = table; |
c3bb4bd7 BP |
897 | return row; |
898 | } | |
899 | ||
900 | static void | |
901 | ovsdb_idl_row_destroy(struct ovsdb_idl_row *row) | |
902 | { | |
903 | if (row) { | |
475281c0 | 904 | ovsdb_idl_row_clear_old(row); |
c3bb4bd7 BP |
905 | hmap_remove(&row->table->rows, &row->hmap_node); |
906 | free(row); | |
907 | } | |
908 | } | |
909 | ||
910 | static void | |
911 | ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json) | |
912 | { | |
913 | const struct ovsdb_idl_table_class *class = row->table->class; | |
914 | size_t i; | |
915 | ||
475281c0 BP |
916 | assert(!row->old && !row->new); |
917 | row->old = row->new = xmalloc(class->n_columns * sizeof *row->old); | |
c3bb4bd7 | 918 | for (i = 0; i < class->n_columns; i++) { |
475281c0 | 919 | ovsdb_datum_init_default(&row->old[i], &class->columns[i].type); |
c3bb4bd7 BP |
920 | } |
921 | ovsdb_idl_row_update(row, row_json); | |
979821c0 | 922 | ovsdb_idl_row_parse(row); |
c3bb4bd7 | 923 | |
0196cc15 | 924 | ovsdb_idl_row_reparse_backrefs(row); |
c3bb4bd7 BP |
925 | } |
926 | ||
927 | static void | |
928 | ovsdb_idl_delete_row(struct ovsdb_idl_row *row) | |
929 | { | |
979821c0 | 930 | ovsdb_idl_row_unparse(row); |
c3bb4bd7 | 931 | ovsdb_idl_row_clear_arcs(row, true); |
475281c0 | 932 | ovsdb_idl_row_clear_old(row); |
c3bb4bd7 BP |
933 | if (list_is_empty(&row->dst_arcs)) { |
934 | ovsdb_idl_row_destroy(row); | |
935 | } else { | |
0196cc15 | 936 | ovsdb_idl_row_reparse_backrefs(row); |
c3bb4bd7 BP |
937 | } |
938 | } | |
939 | ||
c547535a BP |
940 | /* Returns true if a column with mode OVSDB_IDL_MODE_RW changed, false |
941 | * otherwise. */ | |
942 | static bool | |
c3bb4bd7 BP |
943 | ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json) |
944 | { | |
c547535a BP |
945 | bool changed; |
946 | ||
979821c0 | 947 | ovsdb_idl_row_unparse(row); |
c3bb4bd7 | 948 | ovsdb_idl_row_clear_arcs(row, true); |
c547535a | 949 | changed = ovsdb_idl_row_update(row, row_json); |
979821c0 | 950 | ovsdb_idl_row_parse(row); |
c547535a BP |
951 | |
952 | return changed; | |
c3bb4bd7 BP |
953 | } |
954 | ||
955 | static bool | |
956 | may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst) | |
957 | { | |
958 | const struct ovsdb_idl_arc *arc; | |
959 | ||
960 | /* No self-arcs. */ | |
961 | if (src == dst) { | |
962 | return false; | |
963 | } | |
964 | ||
965 | /* No duplicate arcs. | |
966 | * | |
967 | * We only need to test whether the first arc in dst->dst_arcs originates | |
968 | * at 'src', since we add all of the arcs from a given source in a clump | |
979821c0 | 969 | * (in a single call to ovsdb_idl_row_parse()) and new arcs are always |
c3bb4bd7 BP |
970 | * added at the front of the dst_arcs list. */ |
971 | if (list_is_empty(&dst->dst_arcs)) { | |
972 | return true; | |
973 | } | |
974 | arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node); | |
975 | return arc->src != src; | |
976 | } | |
977 | ||
115f1e4d | 978 | static struct ovsdb_idl_table * |
475281c0 BP |
979 | ovsdb_idl_table_from_class(const struct ovsdb_idl *idl, |
980 | const struct ovsdb_idl_table_class *table_class) | |
115f1e4d BP |
981 | { |
982 | return &idl->tables[table_class - idl->class->tables]; | |
983 | } | |
984 | ||
c3bb4bd7 BP |
985 | struct ovsdb_idl_row * |
986 | ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src, | |
987 | struct ovsdb_idl_table_class *dst_table_class, | |
988 | const struct uuid *dst_uuid) | |
989 | { | |
990 | struct ovsdb_idl *idl = src->table->idl; | |
e9011ac8 | 991 | struct ovsdb_idl_table *dst_table; |
c3bb4bd7 BP |
992 | struct ovsdb_idl_arc *arc; |
993 | struct ovsdb_idl_row *dst; | |
994 | ||
475281c0 | 995 | dst_table = ovsdb_idl_table_from_class(idl, dst_table_class); |
e9011ac8 | 996 | dst = ovsdb_idl_get_row(dst_table, dst_uuid); |
979821c0 BP |
997 | if (idl->txn) { |
998 | /* We're being called from ovsdb_idl_txn_write(). We must not update | |
999 | * any arcs, because the transaction will be backed out at commit or | |
1000 | * abort time and we don't want our graph screwed up. | |
1001 | * | |
1002 | * Just return the destination row, if there is one and it has not been | |
1003 | * deleted. */ | |
1004 | if (dst && (hmap_node_is_null(&dst->txn_node) || dst->new)) { | |
1005 | return dst; | |
1006 | } | |
1007 | return NULL; | |
1008 | } else { | |
1009 | /* We're being called from some other context. Update the graph. */ | |
1010 | if (!dst) { | |
1011 | dst = ovsdb_idl_row_create(dst_table, dst_uuid); | |
1012 | } | |
c3bb4bd7 | 1013 | |
979821c0 BP |
1014 | /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */ |
1015 | if (may_add_arc(src, dst)) { | |
1016 | /* The arc *must* be added at the front of the dst_arcs list. See | |
1017 | * ovsdb_idl_row_reparse_backrefs() for details. */ | |
1018 | arc = xmalloc(sizeof *arc); | |
1019 | list_push_front(&src->src_arcs, &arc->src_node); | |
1020 | list_push_front(&dst->dst_arcs, &arc->dst_node); | |
1021 | arc->src = src; | |
1022 | arc->dst = dst; | |
1023 | } | |
1024 | ||
1025 | return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL; | |
c3bb4bd7 | 1026 | } |
979821c0 | 1027 | } |
c3bb4bd7 | 1028 | |
979821c0 BP |
1029 | const struct ovsdb_idl_row * |
1030 | ovsdb_idl_get_row_for_uuid(const struct ovsdb_idl *idl, | |
1031 | const struct ovsdb_idl_table_class *tc, | |
1032 | const struct uuid *uuid) | |
1033 | { | |
1034 | return ovsdb_idl_get_row(ovsdb_idl_table_from_class(idl, tc), uuid); | |
c3bb4bd7 BP |
1035 | } |
1036 | ||
1037 | static struct ovsdb_idl_row * | |
1038 | next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node) | |
1039 | { | |
1040 | for (; node; node = hmap_next(&table->rows, node)) { | |
1041 | struct ovsdb_idl_row *row; | |
1042 | ||
1043 | row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node); | |
f2ba3c0a | 1044 | if (ovsdb_idl_row_exists(row)) { |
c3bb4bd7 BP |
1045 | return row; |
1046 | } | |
1047 | } | |
1048 | return NULL; | |
1049 | } | |
1050 | ||
979821c0 | 1051 | const struct ovsdb_idl_row * |
c3bb4bd7 BP |
1052 | ovsdb_idl_first_row(const struct ovsdb_idl *idl, |
1053 | const struct ovsdb_idl_table_class *table_class) | |
1054 | { | |
475281c0 BP |
1055 | struct ovsdb_idl_table *table |
1056 | = ovsdb_idl_table_from_class(idl, table_class); | |
c3bb4bd7 BP |
1057 | return next_real_row(table, hmap_first(&table->rows)); |
1058 | } | |
1059 | ||
979821c0 | 1060 | const struct ovsdb_idl_row * |
c3bb4bd7 BP |
1061 | ovsdb_idl_next_row(const struct ovsdb_idl_row *row) |
1062 | { | |
1063 | struct ovsdb_idl_table *table = row->table; | |
1064 | ||
1065 | return next_real_row(table, hmap_next(&table->rows, &row->hmap_node)); | |
1066 | } | |
8c3c2f30 BP |
1067 | |
1068 | /* Reads and returns the value of 'column' within 'row'. If an ongoing | |
1069 | * transaction has changed 'column''s value, the modified value is returned. | |
1070 | * | |
1071 | * The caller must not modify or free the returned value. | |
1072 | * | |
1073 | * Various kinds of changes can invalidate the returned value: writing to the | |
1074 | * same 'column' in 'row' (e.g. with ovsdb_idl_txn_write()), deleting 'row' | |
1075 | * (e.g. with ovsdb_idl_txn_delete()), or completing an ongoing transaction | |
1076 | * (e.g. with ovsdb_idl_txn_commit() or ovsdb_idl_txn_abort()). If the | |
1077 | * returned value is needed for a long time, it is best to make a copy of it | |
1078 | * with ovsdb_datum_clone(). */ | |
1079 | const struct ovsdb_datum * | |
1080 | ovsdb_idl_read(const struct ovsdb_idl_row *row, | |
1081 | const struct ovsdb_idl_column *column) | |
1082 | { | |
1083 | const struct ovsdb_idl_table_class *class = row->table->class; | |
1084 | size_t column_idx = column - class->columns; | |
1085 | ||
1086 | assert(row->new != NULL); | |
1087 | assert(column_idx < class->n_columns); | |
1088 | ||
1089 | if (row->written && bitmap_is_set(row->written, column_idx)) { | |
1090 | return &row->new[column_idx]; | |
1091 | } else if (row->old) { | |
1092 | return &row->old[column_idx]; | |
1093 | } else { | |
1094 | return ovsdb_datum_default(&column->type); | |
1095 | } | |
1096 | } | |
1097 | ||
1098 | /* Same as ovsdb_idl_read(), except that it also asserts that 'column' has key | |
1099 | * type 'key_type' and value type 'value_type'. (Scalar and set types will | |
1100 | * have a value type of OVSDB_TYPE_VOID.) | |
1101 | * | |
1102 | * This is useful in code that "knows" that a particular column has a given | |
1103 | * type, so that it will abort if someone changes the column's type without | |
1104 | * updating the code that uses it. */ | |
1105 | const struct ovsdb_datum * | |
1106 | ovsdb_idl_get(const struct ovsdb_idl_row *row, | |
1107 | const struct ovsdb_idl_column *column, | |
1108 | enum ovsdb_atomic_type key_type OVS_UNUSED, | |
1109 | enum ovsdb_atomic_type value_type OVS_UNUSED) | |
1110 | { | |
1111 | assert(column->type.key.type == key_type); | |
1112 | assert(column->type.value.type == value_type); | |
1113 | ||
1114 | return ovsdb_idl_read(row, column); | |
1115 | } | |
475281c0 BP |
1116 | \f |
1117 | /* Transactions. */ | |
1118 | ||
1119 | static void ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn, | |
1120 | enum ovsdb_idl_txn_status); | |
1121 | ||
1122 | const char * | |
1123 | ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status) | |
1124 | { | |
1125 | switch (status) { | |
b54e22e9 BP |
1126 | case TXN_UNCHANGED: |
1127 | return "unchanged"; | |
475281c0 BP |
1128 | case TXN_INCOMPLETE: |
1129 | return "incomplete"; | |
1130 | case TXN_ABORTED: | |
1131 | return "aborted"; | |
1132 | case TXN_SUCCESS: | |
1133 | return "success"; | |
1134 | case TXN_TRY_AGAIN: | |
1135 | return "try again"; | |
1136 | case TXN_ERROR: | |
1137 | return "error"; | |
1138 | } | |
1139 | return "<unknown>"; | |
1140 | } | |
1141 | ||
1142 | struct ovsdb_idl_txn * | |
1143 | ovsdb_idl_txn_create(struct ovsdb_idl *idl) | |
1144 | { | |
1145 | struct ovsdb_idl_txn *txn; | |
1146 | ||
1147 | assert(!idl->txn); | |
1148 | idl->txn = txn = xmalloc(sizeof *txn); | |
0196cc15 | 1149 | txn->request_id = NULL; |
475281c0 | 1150 | txn->idl = idl; |
475281c0 | 1151 | hmap_init(&txn->txn_rows); |
0196cc15 | 1152 | txn->status = TXN_INCOMPLETE; |
91e310a5 | 1153 | txn->error = NULL; |
577aebdf | 1154 | txn->dry_run = false; |
d171b584 | 1155 | ds_init(&txn->comment); |
0196cc15 | 1156 | |
b54e22e9 BP |
1157 | txn->inc_table = NULL; |
1158 | txn->inc_column = NULL; | |
1159 | txn->inc_where = NULL; | |
0196cc15 | 1160 | |
69490970 | 1161 | hmap_init(&txn->inserted_rows); |
0196cc15 | 1162 | |
475281c0 BP |
1163 | return txn; |
1164 | } | |
1165 | ||
e1c0e2d1 BP |
1166 | /* Appends 's', which is treated as a printf()-type format string, to the |
1167 | * comments that will be passed to the OVSDB server when 'txn' is committed. | |
1168 | * (The comment will be committed to the OVSDB log, which "ovsdb-tool | |
1169 | * show-log" can print in a relatively human-readable form.) */ | |
d171b584 | 1170 | void |
e1c0e2d1 | 1171 | ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *txn, const char *s, ...) |
d171b584 | 1172 | { |
e1c0e2d1 BP |
1173 | va_list args; |
1174 | ||
d171b584 BP |
1175 | if (txn->comment.length) { |
1176 | ds_put_char(&txn->comment, '\n'); | |
1177 | } | |
e1c0e2d1 BP |
1178 | |
1179 | va_start(args, s); | |
1180 | ds_put_format_valist(&txn->comment, s, args); | |
1181 | va_end(args); | |
d171b584 BP |
1182 | } |
1183 | ||
577aebdf BP |
1184 | void |
1185 | ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *txn) | |
1186 | { | |
1187 | txn->dry_run = true; | |
1188 | } | |
1189 | ||
b54e22e9 BP |
1190 | void |
1191 | ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn, const char *table, | |
1192 | const char *column, const struct json *where) | |
1193 | { | |
1194 | assert(!txn->inc_table); | |
1195 | txn->inc_table = xstrdup(table); | |
1196 | txn->inc_column = xstrdup(column); | |
1197 | txn->inc_where = where ? json_clone(where) : json_array_create_empty(); | |
1198 | } | |
1199 | ||
475281c0 BP |
1200 | void |
1201 | ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn) | |
1202 | { | |
69490970 BP |
1203 | struct ovsdb_idl_txn_insert *insert, *next; |
1204 | ||
0196cc15 | 1205 | json_destroy(txn->request_id); |
fbd8fd40 BP |
1206 | if (txn->status == TXN_INCOMPLETE) { |
1207 | hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node); | |
1208 | } | |
475281c0 | 1209 | ovsdb_idl_txn_abort(txn); |
d171b584 | 1210 | ds_destroy(&txn->comment); |
91e310a5 | 1211 | free(txn->error); |
b54e22e9 BP |
1212 | free(txn->inc_table); |
1213 | free(txn->inc_column); | |
1214 | json_destroy(txn->inc_where); | |
4e8e4213 | 1215 | HMAP_FOR_EACH_SAFE (insert, next, hmap_node, &txn->inserted_rows) { |
69490970 BP |
1216 | free(insert); |
1217 | } | |
0196cc15 | 1218 | hmap_destroy(&txn->inserted_rows); |
475281c0 BP |
1219 | free(txn); |
1220 | } | |
1221 | ||
586bb84a BP |
1222 | void |
1223 | ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *txn) | |
1224 | { | |
1225 | if (txn->status != TXN_INCOMPLETE) { | |
1226 | poll_immediate_wake(); | |
1227 | } | |
1228 | } | |
1229 | ||
475281c0 BP |
1230 | static struct json * |
1231 | where_uuid_equals(const struct uuid *uuid) | |
1232 | { | |
1233 | return | |
1234 | json_array_create_1( | |
1235 | json_array_create_3( | |
1236 | json_string_create("_uuid"), | |
1237 | json_string_create("=="), | |
1238 | json_array_create_2( | |
1239 | json_string_create("uuid"), | |
1240 | json_string_create_nocopy( | |
1241 | xasprintf(UUID_FMT, UUID_ARGS(uuid)))))); | |
1242 | } | |
1243 | ||
1244 | static char * | |
1245 | uuid_name_from_uuid(const struct uuid *uuid) | |
1246 | { | |
1247 | char *name; | |
1248 | char *p; | |
1249 | ||
1250 | name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid)); | |
1251 | for (p = name; *p != '\0'; p++) { | |
1252 | if (*p == '-') { | |
1253 | *p = '_'; | |
1254 | } | |
1255 | } | |
1256 | ||
1257 | return name; | |
1258 | } | |
1259 | ||
1260 | static const struct ovsdb_idl_row * | |
1261 | ovsdb_idl_txn_get_row(const struct ovsdb_idl_txn *txn, const struct uuid *uuid) | |
1262 | { | |
1263 | const struct ovsdb_idl_row *row; | |
1264 | ||
4e8e4213 | 1265 | HMAP_FOR_EACH_WITH_HASH (row, txn_node, uuid_hash(uuid), &txn->txn_rows) { |
475281c0 BP |
1266 | if (uuid_equals(&row->uuid, uuid)) { |
1267 | return row; | |
1268 | } | |
1269 | } | |
1270 | return NULL; | |
1271 | } | |
1272 | ||
1273 | /* XXX there must be a cleaner way to do this */ | |
1274 | static struct json * | |
1275 | substitute_uuids(struct json *json, const struct ovsdb_idl_txn *txn) | |
1276 | { | |
1277 | if (json->type == JSON_ARRAY) { | |
1278 | struct uuid uuid; | |
1279 | size_t i; | |
1280 | ||
1281 | if (json->u.array.n == 2 | |
1282 | && json->u.array.elems[0]->type == JSON_STRING | |
1283 | && json->u.array.elems[1]->type == JSON_STRING | |
1284 | && !strcmp(json->u.array.elems[0]->u.string, "uuid") | |
1285 | && uuid_from_string(&uuid, json->u.array.elems[1]->u.string)) { | |
1286 | const struct ovsdb_idl_row *row; | |
1287 | ||
1288 | row = ovsdb_idl_txn_get_row(txn, &uuid); | |
1289 | if (row && !row->old && row->new) { | |
1290 | json_destroy(json); | |
1291 | ||
1292 | return json_array_create_2( | |
1293 | json_string_create("named-uuid"), | |
1294 | json_string_create_nocopy(uuid_name_from_uuid(&uuid))); | |
1295 | } | |
1296 | } | |
1297 | ||
1298 | for (i = 0; i < json->u.array.n; i++) { | |
1299 | json->u.array.elems[i] = substitute_uuids(json->u.array.elems[i], | |
1300 | txn); | |
1301 | } | |
1302 | } else if (json->type == JSON_OBJECT) { | |
1303 | struct shash_node *node; | |
1304 | ||
1305 | SHASH_FOR_EACH (node, json_object(json)) { | |
1306 | node->data = substitute_uuids(node->data, txn); | |
1307 | } | |
1308 | } | |
1309 | return json; | |
1310 | } | |
1311 | ||
1312 | static void | |
1313 | ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn) | |
1314 | { | |
1315 | struct ovsdb_idl_row *row, *next; | |
1316 | ||
979821c0 BP |
1317 | /* This must happen early. Otherwise, ovsdb_idl_row_parse() will call an |
1318 | * ovsdb_idl_column's 'parse' function, which will call | |
1319 | * ovsdb_idl_get_row_arc(), which will seen that the IDL is in a | |
1320 | * transaction and fail to update the graph. */ | |
1321 | txn->idl->txn = NULL; | |
1322 | ||
4e8e4213 | 1323 | HMAP_FOR_EACH_SAFE (row, next, txn_node, &txn->txn_rows) { |
979821c0 BP |
1324 | if (row->old) { |
1325 | if (row->written) { | |
1326 | ovsdb_idl_row_unparse(row); | |
1327 | ovsdb_idl_row_clear_arcs(row, false); | |
1328 | ovsdb_idl_row_parse(row); | |
1329 | } | |
1330 | } else { | |
0196cc15 | 1331 | ovsdb_idl_row_unparse(row); |
8bc915de | 1332 | } |
475281c0 BP |
1333 | ovsdb_idl_row_clear_new(row); |
1334 | ||
1335 | free(row->prereqs); | |
1336 | row->prereqs = NULL; | |
1337 | ||
1338 | free(row->written); | |
1339 | row->written = NULL; | |
1340 | ||
1341 | hmap_remove(&txn->txn_rows, &row->txn_node); | |
1342 | hmap_node_nullify(&row->txn_node); | |
0196cc15 BP |
1343 | if (!row->old) { |
1344 | hmap_remove(&row->table->rows, &row->hmap_node); | |
1345 | free(row); | |
1346 | } | |
475281c0 BP |
1347 | } |
1348 | hmap_destroy(&txn->txn_rows); | |
1349 | hmap_init(&txn->txn_rows); | |
1350 | } | |
1351 | ||
1352 | enum ovsdb_idl_txn_status | |
1353 | ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn) | |
1354 | { | |
1355 | struct ovsdb_idl_row *row; | |
1356 | struct json *operations; | |
1357 | bool any_updates; | |
475281c0 BP |
1358 | |
1359 | if (txn != txn->idl->txn) { | |
1360 | return txn->status; | |
1361 | } | |
1362 | ||
9cb53f26 BP |
1363 | operations = json_array_create_1( |
1364 | json_string_create(txn->idl->class->database)); | |
475281c0 BP |
1365 | |
1366 | /* Add prerequisites and declarations of new rows. */ | |
4e8e4213 | 1367 | HMAP_FOR_EACH (row, txn_node, &txn->txn_rows) { |
475281c0 BP |
1368 | /* XXX check that deleted rows exist even if no prereqs? */ |
1369 | if (row->prereqs) { | |
1370 | const struct ovsdb_idl_table_class *class = row->table->class; | |
1371 | size_t n_columns = class->n_columns; | |
1372 | struct json *op, *columns, *row_json; | |
1373 | size_t idx; | |
1374 | ||
1375 | op = json_object_create(); | |
1376 | json_array_add(operations, op); | |
1377 | json_object_put_string(op, "op", "wait"); | |
1378 | json_object_put_string(op, "table", class->name); | |
1379 | json_object_put(op, "timeout", json_integer_create(0)); | |
1380 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1381 | json_object_put_string(op, "until", "=="); | |
1382 | columns = json_array_create_empty(); | |
1383 | json_object_put(op, "columns", columns); | |
1384 | row_json = json_object_create(); | |
1385 | json_object_put(op, "rows", json_array_create_1(row_json)); | |
1386 | ||
1387 | BITMAP_FOR_EACH_1 (idx, n_columns, row->prereqs) { | |
1388 | const struct ovsdb_idl_column *column = &class->columns[idx]; | |
1389 | json_array_add(columns, json_string_create(column->name)); | |
1390 | json_object_put(row_json, column->name, | |
1391 | ovsdb_datum_to_json(&row->old[idx], | |
1392 | &column->type)); | |
1393 | } | |
1394 | } | |
475281c0 BP |
1395 | } |
1396 | ||
1397 | /* Add updates. */ | |
1398 | any_updates = false; | |
4e8e4213 | 1399 | HMAP_FOR_EACH (row, txn_node, &txn->txn_rows) { |
475281c0 | 1400 | const struct ovsdb_idl_table_class *class = row->table->class; |
475281c0 BP |
1401 | |
1402 | if (row->old == row->new) { | |
1403 | continue; | |
1404 | } else if (!row->new) { | |
1405 | struct json *op = json_object_create(); | |
1406 | json_object_put_string(op, "op", "delete"); | |
1407 | json_object_put_string(op, "table", class->name); | |
1408 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1409 | json_array_add(operations, op); | |
f0f54cb4 | 1410 | any_updates = true; |
475281c0 | 1411 | } else { |
f0f54cb4 BP |
1412 | struct json *row_json; |
1413 | struct json *op; | |
1414 | size_t idx; | |
1415 | ||
1416 | op = json_object_create(); | |
1417 | json_object_put_string(op, "op", row->old ? "update" : "insert"); | |
1418 | json_object_put_string(op, "table", class->name); | |
1419 | if (row->old) { | |
1420 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1421 | } else { | |
69490970 BP |
1422 | struct ovsdb_idl_txn_insert *insert; |
1423 | ||
f0f54cb4 BP |
1424 | json_object_put(op, "uuid-name", |
1425 | json_string_create_nocopy( | |
1426 | uuid_name_from_uuid(&row->uuid))); | |
69490970 BP |
1427 | |
1428 | insert = xmalloc(sizeof *insert); | |
1429 | insert->dummy = row->uuid; | |
9cb53f26 | 1430 | insert->op_index = operations->u.array.n - 1; |
69490970 BP |
1431 | uuid_zero(&insert->real); |
1432 | hmap_insert(&txn->inserted_rows, &insert->hmap_node, | |
1433 | uuid_hash(&insert->dummy)); | |
f0f54cb4 BP |
1434 | } |
1435 | row_json = json_object_create(); | |
1436 | json_object_put(op, "row", row_json); | |
1437 | ||
35c2ce98 JG |
1438 | if (row->written) { |
1439 | BITMAP_FOR_EACH_1 (idx, class->n_columns, row->written) { | |
1440 | const struct ovsdb_idl_column *column = | |
1441 | &class->columns[idx]; | |
1442 | ||
1443 | if (row->old | |
1444 | ? !ovsdb_datum_equals(&row->old[idx], &row->new[idx], | |
1445 | &column->type) | |
1446 | : !ovsdb_datum_is_default(&row->new[idx], | |
1447 | &column->type)) { | |
1448 | json_object_put(row_json, column->name, | |
1449 | substitute_uuids( | |
1450 | ovsdb_datum_to_json(&row->new[idx], | |
1451 | &column->type), | |
1452 | txn)); | |
1453 | } | |
475281c0 | 1454 | } |
f0f54cb4 BP |
1455 | } |
1456 | ||
1457 | if (!row->old || !shash_is_empty(json_object(row_json))) { | |
1458 | json_array_add(operations, op); | |
1459 | any_updates = true; | |
1460 | } else { | |
1461 | json_destroy(op); | |
475281c0 BP |
1462 | } |
1463 | } | |
1464 | } | |
1465 | ||
b54e22e9 BP |
1466 | /* Add increment. */ |
1467 | if (txn->inc_table && any_updates) { | |
1468 | struct json *op; | |
1469 | ||
9cb53f26 | 1470 | txn->inc_index = operations->u.array.n - 1; |
b54e22e9 BP |
1471 | |
1472 | op = json_object_create(); | |
1473 | json_object_put_string(op, "op", "mutate"); | |
1474 | json_object_put_string(op, "table", txn->inc_table); | |
1475 | json_object_put(op, "where", | |
1476 | substitute_uuids(json_clone(txn->inc_where), txn)); | |
1477 | json_object_put(op, "mutations", | |
1478 | json_array_create_1( | |
1479 | json_array_create_3( | |
1480 | json_string_create(txn->inc_column), | |
1481 | json_string_create("+="), | |
1482 | json_integer_create(1)))); | |
1483 | json_array_add(operations, op); | |
1484 | ||
1485 | op = json_object_create(); | |
1486 | json_object_put_string(op, "op", "select"); | |
1487 | json_object_put_string(op, "table", txn->inc_table); | |
1488 | json_object_put(op, "where", | |
1489 | substitute_uuids(json_clone(txn->inc_where), txn)); | |
1490 | json_object_put(op, "columns", | |
1491 | json_array_create_1(json_string_create( | |
1492 | txn->inc_column))); | |
1493 | json_array_add(operations, op); | |
1494 | } | |
1495 | ||
d171b584 BP |
1496 | if (txn->comment.length) { |
1497 | struct json *op = json_object_create(); | |
1498 | json_object_put_string(op, "op", "comment"); | |
1499 | json_object_put_string(op, "comment", ds_cstr(&txn->comment)); | |
1500 | json_array_add(operations, op); | |
1501 | } | |
1502 | ||
577aebdf BP |
1503 | if (txn->dry_run) { |
1504 | struct json *op = json_object_create(); | |
1505 | json_object_put_string(op, "op", "abort"); | |
1506 | json_array_add(operations, op); | |
1507 | } | |
1508 | ||
72c6edc5 | 1509 | if (!any_updates) { |
b54e22e9 | 1510 | txn->status = TXN_UNCHANGED; |
2f3ca7ea | 1511 | json_destroy(operations); |
72c6edc5 BP |
1512 | } else if (!jsonrpc_session_send( |
1513 | txn->idl->session, | |
1514 | jsonrpc_create_request( | |
1515 | "transact", operations, &txn->request_id))) { | |
1516 | hmap_insert(&txn->idl->outstanding_txns, &txn->hmap_node, | |
1517 | json_hash(txn->request_id, 0)); | |
1518 | } else { | |
79554078 | 1519 | txn->status = TXN_TRY_AGAIN; |
72c6edc5 | 1520 | } |
475281c0 | 1521 | |
475281c0 | 1522 | ovsdb_idl_txn_disassemble(txn); |
475281c0 BP |
1523 | return txn->status; |
1524 | } | |
1525 | ||
af96ccd2 BP |
1526 | /* Attempts to commit 'txn', blocking until the commit either succeeds or |
1527 | * fails. Returns the final commit status, which may be any TXN_* value other | |
1528 | * than TXN_INCOMPLETE. */ | |
1529 | enum ovsdb_idl_txn_status | |
1530 | ovsdb_idl_txn_commit_block(struct ovsdb_idl_txn *txn) | |
1531 | { | |
1532 | enum ovsdb_idl_txn_status status; | |
1533 | ||
b302749b | 1534 | fatal_signal_run(); |
af96ccd2 BP |
1535 | while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) { |
1536 | ovsdb_idl_run(txn->idl); | |
1537 | ovsdb_idl_wait(txn->idl); | |
1538 | ovsdb_idl_txn_wait(txn); | |
1539 | poll_block(); | |
1540 | } | |
1541 | return status; | |
1542 | } | |
1543 | ||
b54e22e9 BP |
1544 | int64_t |
1545 | ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn) | |
1546 | { | |
1547 | assert(txn->status == TXN_SUCCESS); | |
1548 | return txn->inc_new_value; | |
1549 | } | |
1550 | ||
475281c0 BP |
1551 | void |
1552 | ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn) | |
1553 | { | |
1554 | ovsdb_idl_txn_disassemble(txn); | |
1555 | if (txn->status == TXN_INCOMPLETE) { | |
1556 | txn->status = TXN_ABORTED; | |
1557 | } | |
1558 | } | |
1559 | ||
91e310a5 BP |
1560 | const char * |
1561 | ovsdb_idl_txn_get_error(const struct ovsdb_idl_txn *txn) | |
1562 | { | |
1563 | if (txn->status != TXN_ERROR) { | |
1564 | return ovsdb_idl_txn_status_to_string(txn->status); | |
1565 | } else if (txn->error) { | |
1566 | return txn->error; | |
1567 | } else { | |
1568 | return "no error details available"; | |
1569 | } | |
1570 | } | |
1571 | ||
1572 | static void | |
1573 | ovsdb_idl_txn_set_error_json(struct ovsdb_idl_txn *txn, | |
1574 | const struct json *json) | |
1575 | { | |
1576 | if (txn->error == NULL) { | |
1577 | txn->error = json_to_string(json, JSSF_SORT); | |
1578 | } | |
1579 | } | |
1580 | ||
69490970 BP |
1581 | /* For transaction 'txn' that completed successfully, finds and returns the |
1582 | * permanent UUID that the database assigned to a newly inserted row, given the | |
1583 | * 'uuid' that ovsdb_idl_txn_insert() assigned locally to that row. | |
1584 | * | |
1585 | * Returns NULL if 'uuid' is not a UUID assigned by ovsdb_idl_txn_insert() or | |
1586 | * if it was assigned by that function and then deleted by | |
1587 | * ovsdb_idl_txn_delete() within the same transaction. (Rows that are inserted | |
1588 | * and then deleted within a single transaction are never sent to the database | |
1589 | * server, so it never assigns them a permanent UUID.) */ | |
1590 | const struct uuid * | |
1591 | ovsdb_idl_txn_get_insert_uuid(const struct ovsdb_idl_txn *txn, | |
1592 | const struct uuid *uuid) | |
1593 | { | |
1594 | const struct ovsdb_idl_txn_insert *insert; | |
1595 | ||
1596 | assert(txn->status == TXN_SUCCESS || txn->status == TXN_UNCHANGED); | |
4e8e4213 | 1597 | HMAP_FOR_EACH_IN_BUCKET (insert, hmap_node, |
69490970 BP |
1598 | uuid_hash(uuid), &txn->inserted_rows) { |
1599 | if (uuid_equals(uuid, &insert->dummy)) { | |
1600 | return &insert->real; | |
1601 | } | |
1602 | } | |
1603 | return NULL; | |
1604 | } | |
1605 | ||
475281c0 BP |
1606 | static void |
1607 | ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn, | |
1608 | enum ovsdb_idl_txn_status status) | |
1609 | { | |
1610 | txn->status = status; | |
1611 | hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node); | |
1612 | } | |
1613 | ||
b827c67b BP |
1614 | /* Writes 'datum' to the specified 'column' in 'row_'. Updates both 'row_' |
1615 | * itself and the structs derived from it (e.g. the "struct ovsrec_*", for | |
1616 | * ovs-vswitchd). | |
1617 | * | |
1618 | * 'datum' must have the correct type for its column. The IDL does not check | |
1619 | * that it meets schema constraints, but ovsdb-server will do so at commit time | |
1620 | * so it had better be correct. | |
1621 | * | |
1622 | * A transaction must be in progress. Replication of 'column' must not have | |
1623 | * been disabled (by calling ovsdb_idl_omit()). | |
1624 | * | |
1625 | * Usually this function is used indirectly through one of the "set" functions | |
1626 | * generated by ovsdb-idlc. */ | |
979821c0 BP |
1627 | void |
1628 | ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_, | |
475281c0 BP |
1629 | const struct ovsdb_idl_column *column, |
1630 | struct ovsdb_datum *datum) | |
1631 | { | |
979821c0 | 1632 | struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_; |
475281c0 BP |
1633 | const struct ovsdb_idl_table_class *class = row->table->class; |
1634 | size_t column_idx = column - class->columns; | |
1635 | ||
979821c0 | 1636 | assert(row->new != NULL); |
bd76d25d | 1637 | assert(column_idx < class->n_columns); |
ef73f86c BP |
1638 | assert(row->old == NULL || |
1639 | row->table->modes[column_idx] & OVSDB_IDL_MONITOR); | |
c547535a | 1640 | |
475281c0 BP |
1641 | if (hmap_node_is_null(&row->txn_node)) { |
1642 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1643 | uuid_hash(&row->uuid)); | |
1644 | } | |
1645 | if (row->old == row->new) { | |
1646 | row->new = xmalloc(class->n_columns * sizeof *row->new); | |
1647 | } | |
1648 | if (!row->written) { | |
1649 | row->written = bitmap_allocate(class->n_columns); | |
1650 | } | |
1651 | if (bitmap_is_set(row->written, column_idx)) { | |
1652 | ovsdb_datum_destroy(&row->new[column_idx], &column->type); | |
1653 | } else { | |
1654 | bitmap_set1(row->written, column_idx); | |
1655 | } | |
1656 | row->new[column_idx] = *datum; | |
979821c0 BP |
1657 | (column->unparse)(row); |
1658 | (column->parse)(row, &row->new[column_idx]); | |
475281c0 BP |
1659 | } |
1660 | ||
b827c67b BP |
1661 | /* Causes the original contents of 'column' in 'row_' to be verified as a |
1662 | * prerequisite to completing the transaction. That is, if 'column' in 'row_' | |
1663 | * changed (or if 'row_' was deleted) between the time that the IDL originally | |
1664 | * read its contents and the time that the transaction commits, then the | |
1665 | * transaction aborts and ovsdb_idl_txn_commit() returns TXN_TRY_AGAIN. | |
1666 | * | |
1667 | * The intention is that, to ensure that no transaction commits based on dirty | |
1668 | * reads, an application should call ovsdb_idl_txn_verify() on each data item | |
1669 | * read as part of a read-modify-write operation. | |
1670 | * | |
1671 | * In some cases ovsdb_idl_txn_verify() reduces to a no-op, because the current | |
1672 | * value of 'column' is already known: | |
1673 | * | |
1674 | * - If 'row_' is a row created by the current transaction (returned by | |
1675 | * ovsdb_idl_txn_insert()). | |
1676 | * | |
1677 | * - If 'column' has already been modified (with ovsdb_idl_txn_write()) | |
1678 | * within the current transaction. | |
1679 | * | |
1680 | * Because of the latter property, always call ovsdb_idl_txn_verify() *before* | |
1681 | * ovsdb_idl_txn_write() for a given read-modify-write. | |
1682 | * | |
1683 | * A transaction must be in progress. | |
1684 | * | |
1685 | * Usually this function is used indirectly through one of the "verify" | |
1686 | * functions generated by ovsdb-idlc. */ | |
475281c0 BP |
1687 | void |
1688 | ovsdb_idl_txn_verify(const struct ovsdb_idl_row *row_, | |
1689 | const struct ovsdb_idl_column *column) | |
1690 | { | |
1691 | struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_; | |
1692 | const struct ovsdb_idl_table_class *class = row->table->class; | |
1693 | size_t column_idx = column - class->columns; | |
1694 | ||
979821c0 | 1695 | assert(row->new != NULL); |
ee21cfa7 BP |
1696 | assert(row->old == NULL || |
1697 | row->table->modes[column_idx] & OVSDB_IDL_MONITOR); | |
475281c0 BP |
1698 | if (!row->old |
1699 | || (row->written && bitmap_is_set(row->written, column_idx))) { | |
1700 | return; | |
1701 | } | |
1702 | ||
1703 | if (hmap_node_is_null(&row->txn_node)) { | |
1704 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1705 | uuid_hash(&row->uuid)); | |
1706 | } | |
1707 | if (!row->prereqs) { | |
1708 | row->prereqs = bitmap_allocate(class->n_columns); | |
1709 | } | |
1710 | bitmap_set1(row->prereqs, column_idx); | |
1711 | } | |
1712 | ||
b827c67b BP |
1713 | /* Deletes 'row_' from its table. May free 'row_', so it must not be |
1714 | * accessed afterward. | |
1715 | * | |
1716 | * A transaction must be in progress. | |
1717 | * | |
1718 | * Usually this function is used indirectly through one of the "delete" | |
1719 | * functions generated by ovsdb-idlc. */ | |
475281c0 | 1720 | void |
9e336f49 | 1721 | ovsdb_idl_txn_delete(const struct ovsdb_idl_row *row_) |
475281c0 | 1722 | { |
9e336f49 BP |
1723 | struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_; |
1724 | ||
979821c0 | 1725 | assert(row->new != NULL); |
475281c0 | 1726 | if (!row->old) { |
0196cc15 | 1727 | ovsdb_idl_row_unparse(row); |
475281c0 BP |
1728 | ovsdb_idl_row_clear_new(row); |
1729 | assert(!row->prereqs); | |
979821c0 | 1730 | hmap_remove(&row->table->rows, &row->hmap_node); |
475281c0 BP |
1731 | hmap_remove(&row->table->idl->txn->txn_rows, &row->txn_node); |
1732 | free(row); | |
c7f7adb7 | 1733 | return; |
475281c0 BP |
1734 | } |
1735 | if (hmap_node_is_null(&row->txn_node)) { | |
1736 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1737 | uuid_hash(&row->uuid)); | |
1738 | } | |
1ebeed63 BP |
1739 | ovsdb_idl_row_clear_new(row); |
1740 | row->new = NULL; | |
475281c0 BP |
1741 | } |
1742 | ||
b827c67b BP |
1743 | /* Inserts and returns a new row in the table with the specified 'class' in the |
1744 | * database with open transaction 'txn'. | |
1745 | * | |
1746 | * The new row is assigned a provisional UUID. If 'uuid' is null then one is | |
1747 | * randomly generated; otherwise 'uuid' should specify a randomly generated | |
1748 | * UUID not otherwise in use. ovsdb-server will assign a different UUID when | |
1749 | * 'txn' is committed, but the IDL will replace any uses of the provisional | |
1750 | * UUID in the data to be to be committed by the UUID assigned by | |
1751 | * ovsdb-server. | |
1752 | * | |
1753 | * Usually this function is used indirectly through one of the "insert" | |
1754 | * functions generated by ovsdb-idlc. */ | |
9e336f49 | 1755 | const struct ovsdb_idl_row * |
475281c0 | 1756 | ovsdb_idl_txn_insert(struct ovsdb_idl_txn *txn, |
ce5a3e38 BP |
1757 | const struct ovsdb_idl_table_class *class, |
1758 | const struct uuid *uuid) | |
475281c0 BP |
1759 | { |
1760 | struct ovsdb_idl_row *row = ovsdb_idl_row_create__(class); | |
ce5a3e38 BP |
1761 | |
1762 | if (uuid) { | |
1763 | assert(!ovsdb_idl_txn_get_row(txn, uuid)); | |
1764 | row->uuid = *uuid; | |
1765 | } else { | |
1766 | uuid_generate(&row->uuid); | |
1767 | } | |
1768 | ||
475281c0 BP |
1769 | row->table = ovsdb_idl_table_from_class(txn->idl, class); |
1770 | row->new = xmalloc(class->n_columns * sizeof *row->new); | |
979821c0 | 1771 | hmap_insert(&row->table->rows, &row->hmap_node, uuid_hash(&row->uuid)); |
475281c0 BP |
1772 | hmap_insert(&txn->txn_rows, &row->txn_node, uuid_hash(&row->uuid)); |
1773 | return row; | |
1774 | } | |
1775 | ||
1776 | static void | |
1777 | ovsdb_idl_txn_abort_all(struct ovsdb_idl *idl) | |
1778 | { | |
1779 | struct ovsdb_idl_txn *txn; | |
1780 | ||
4e8e4213 | 1781 | HMAP_FOR_EACH (txn, hmap_node, &idl->outstanding_txns) { |
475281c0 BP |
1782 | ovsdb_idl_txn_complete(txn, TXN_TRY_AGAIN); |
1783 | } | |
1784 | } | |
1785 | ||
1786 | static struct ovsdb_idl_txn * | |
1787 | ovsdb_idl_txn_find(struct ovsdb_idl *idl, const struct json *id) | |
1788 | { | |
1789 | struct ovsdb_idl_txn *txn; | |
1790 | ||
4e8e4213 | 1791 | HMAP_FOR_EACH_WITH_HASH (txn, hmap_node, |
475281c0 BP |
1792 | json_hash(id, 0), &idl->outstanding_txns) { |
1793 | if (json_equal(id, txn->request_id)) { | |
1794 | return txn; | |
1795 | } | |
1796 | } | |
1797 | return NULL; | |
1798 | } | |
1799 | ||
b54e22e9 BP |
1800 | static bool |
1801 | check_json_type(const struct json *json, enum json_type type, const char *name) | |
1802 | { | |
1803 | if (!json) { | |
1804 | VLOG_WARN_RL(&syntax_rl, "%s is missing", name); | |
1805 | return false; | |
1806 | } else if (json->type != type) { | |
1807 | VLOG_WARN_RL(&syntax_rl, "%s is %s instead of %s", | |
1808 | name, json_type_to_string(json->type), | |
1809 | json_type_to_string(type)); | |
1810 | return false; | |
1811 | } else { | |
1812 | return true; | |
1813 | } | |
1814 | } | |
1815 | ||
1816 | static bool | |
1817 | ovsdb_idl_txn_process_inc_reply(struct ovsdb_idl_txn *txn, | |
1818 | const struct json_array *results) | |
1819 | { | |
1820 | struct json *count, *rows, *row, *column; | |
1821 | struct shash *mutate, *select; | |
1822 | ||
1823 | if (txn->inc_index + 2 > results->n) { | |
1824 | VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations " | |
6fce4487 | 1825 | "for increment (has %zu, needs %u)", |
b54e22e9 BP |
1826 | results->n, txn->inc_index + 2); |
1827 | return false; | |
1828 | } | |
1829 | ||
69490970 | 1830 | /* We know that this is a JSON object because the loop in |
b54e22e9 BP |
1831 | * ovsdb_idl_txn_process_reply() checked. */ |
1832 | mutate = json_object(results->elems[txn->inc_index]); | |
1833 | count = shash_find_data(mutate, "count"); | |
1834 | if (!check_json_type(count, JSON_INTEGER, "\"mutate\" reply \"count\"")) { | |
1835 | return false; | |
1836 | } | |
1837 | if (count->u.integer != 1) { | |
1838 | VLOG_WARN_RL(&syntax_rl, | |
6fce4487 | 1839 | "\"mutate\" reply \"count\" is %lld instead of 1", |
b54e22e9 BP |
1840 | count->u.integer); |
1841 | return false; | |
1842 | } | |
1843 | ||
1844 | select = json_object(results->elems[txn->inc_index + 1]); | |
1845 | rows = shash_find_data(select, "rows"); | |
1846 | if (!check_json_type(rows, JSON_ARRAY, "\"select\" reply \"rows\"")) { | |
1847 | return false; | |
1848 | } | |
1849 | if (rows->u.array.n != 1) { | |
6fce4487 | 1850 | VLOG_WARN_RL(&syntax_rl, "\"select\" reply \"rows\" has %zu elements " |
b54e22e9 BP |
1851 | "instead of 1", |
1852 | rows->u.array.n); | |
1853 | return false; | |
1854 | } | |
1855 | row = rows->u.array.elems[0]; | |
1856 | if (!check_json_type(row, JSON_OBJECT, "\"select\" reply row")) { | |
1857 | return false; | |
1858 | } | |
1859 | column = shash_find_data(json_object(row), txn->inc_column); | |
1860 | if (!check_json_type(column, JSON_INTEGER, | |
1861 | "\"select\" reply inc column")) { | |
1862 | return false; | |
1863 | } | |
1864 | txn->inc_new_value = column->u.integer; | |
1865 | return true; | |
1866 | } | |
1867 | ||
69490970 BP |
1868 | static bool |
1869 | ovsdb_idl_txn_process_insert_reply(struct ovsdb_idl_txn_insert *insert, | |
1870 | const struct json_array *results) | |
1871 | { | |
bd76d25d | 1872 | static const struct ovsdb_base_type uuid_type = OVSDB_BASE_UUID_INIT; |
69490970 BP |
1873 | struct ovsdb_error *error; |
1874 | struct json *json_uuid; | |
1875 | union ovsdb_atom uuid; | |
1876 | struct shash *reply; | |
1877 | ||
1878 | if (insert->op_index >= results->n) { | |
1879 | VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations " | |
6fce4487 | 1880 | "for insert (has %zu, needs %u)", |
69490970 BP |
1881 | results->n, insert->op_index); |
1882 | return false; | |
1883 | } | |
1884 | ||
1885 | /* We know that this is a JSON object because the loop in | |
1886 | * ovsdb_idl_txn_process_reply() checked. */ | |
1887 | reply = json_object(results->elems[insert->op_index]); | |
1888 | json_uuid = shash_find_data(reply, "uuid"); | |
1889 | if (!check_json_type(json_uuid, JSON_ARRAY, "\"insert\" reply \"uuid\"")) { | |
1890 | return false; | |
1891 | } | |
1892 | ||
bd76d25d | 1893 | error = ovsdb_atom_from_json(&uuid, &uuid_type, json_uuid, NULL); |
69490970 BP |
1894 | if (error) { |
1895 | char *s = ovsdb_error_to_string(error); | |
1896 | VLOG_WARN_RL(&syntax_rl, "\"insert\" reply \"uuid\" is not a JSON " | |
1897 | "UUID: %s", s); | |
1898 | free(s); | |
1899 | return false; | |
1900 | } | |
1901 | ||
1902 | insert->real = uuid.uuid; | |
1903 | ||
1904 | return true; | |
1905 | } | |
b54e22e9 | 1906 | |
475281c0 BP |
1907 | static bool |
1908 | ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl, | |
1909 | const struct jsonrpc_msg *msg) | |
1910 | { | |
1911 | struct ovsdb_idl_txn *txn; | |
1912 | enum ovsdb_idl_txn_status status; | |
1913 | ||
1914 | txn = ovsdb_idl_txn_find(idl, msg->id); | |
1915 | if (!txn) { | |
1916 | return false; | |
1917 | } | |
1918 | ||
1919 | if (msg->type == JSONRPC_ERROR) { | |
1920 | status = TXN_ERROR; | |
1921 | } else if (msg->result->type != JSON_ARRAY) { | |
1922 | VLOG_WARN_RL(&syntax_rl, "reply to \"transact\" is not JSON array"); | |
1923 | status = TXN_ERROR; | |
1924 | } else { | |
69490970 | 1925 | struct json_array *ops = &msg->result->u.array; |
475281c0 BP |
1926 | int hard_errors = 0; |
1927 | int soft_errors = 0; | |
1928 | size_t i; | |
1929 | ||
69490970 BP |
1930 | for (i = 0; i < ops->n; i++) { |
1931 | struct json *op = ops->elems[i]; | |
475281c0 | 1932 | |
69490970 | 1933 | if (op->type == JSON_NULL) { |
475281c0 BP |
1934 | /* This isn't an error in itself but indicates that some prior |
1935 | * operation failed, so make sure that we know about it. */ | |
1936 | soft_errors++; | |
69490970 | 1937 | } else if (op->type == JSON_OBJECT) { |
475281c0 BP |
1938 | struct json *error; |
1939 | ||
69490970 | 1940 | error = shash_find_data(json_object(op), "error"); |
475281c0 BP |
1941 | if (error) { |
1942 | if (error->type == JSON_STRING) { | |
1943 | if (!strcmp(error->u.string, "timed out")) { | |
1944 | soft_errors++; | |
577aebdf | 1945 | } else if (strcmp(error->u.string, "aborted")) { |
475281c0 | 1946 | hard_errors++; |
91e310a5 | 1947 | ovsdb_idl_txn_set_error_json(txn, op); |
475281c0 BP |
1948 | } |
1949 | } else { | |
1950 | hard_errors++; | |
91e310a5 | 1951 | ovsdb_idl_txn_set_error_json(txn, op); |
475281c0 BP |
1952 | VLOG_WARN_RL(&syntax_rl, |
1953 | "\"error\" in reply is not JSON string"); | |
1954 | } | |
1955 | } | |
1956 | } else { | |
1957 | hard_errors++; | |
91e310a5 | 1958 | ovsdb_idl_txn_set_error_json(txn, op); |
475281c0 BP |
1959 | VLOG_WARN_RL(&syntax_rl, |
1960 | "operation reply is not JSON null or object"); | |
1961 | } | |
1962 | } | |
1963 | ||
69490970 BP |
1964 | if (!soft_errors && !hard_errors) { |
1965 | struct ovsdb_idl_txn_insert *insert; | |
1966 | ||
1967 | if (txn->inc_table && !ovsdb_idl_txn_process_inc_reply(txn, ops)) { | |
1968 | hard_errors++; | |
1969 | } | |
1970 | ||
4e8e4213 | 1971 | HMAP_FOR_EACH (insert, hmap_node, &txn->inserted_rows) { |
69490970 BP |
1972 | if (!ovsdb_idl_txn_process_insert_reply(insert, ops)) { |
1973 | hard_errors++; | |
1974 | } | |
1975 | } | |
b54e22e9 BP |
1976 | } |
1977 | ||
475281c0 BP |
1978 | status = (hard_errors ? TXN_ERROR |
1979 | : soft_errors ? TXN_TRY_AGAIN | |
1980 | : TXN_SUCCESS); | |
1981 | } | |
1982 | ||
1983 | ovsdb_idl_txn_complete(txn, status); | |
1984 | return true; | |
1985 | } | |
76c91af9 BP |
1986 | |
1987 | struct ovsdb_idl_txn * | |
1988 | ovsdb_idl_txn_get(const struct ovsdb_idl_row *row) | |
1989 | { | |
1990 | struct ovsdb_idl_txn *txn = row->table->idl->txn; | |
1991 | assert(txn != NULL); | |
1992 | return txn; | |
1993 | } | |
1e86ae6f BP |
1994 | |
1995 | struct ovsdb_idl * | |
1996 | ovsdb_idl_txn_get_idl (struct ovsdb_idl_txn *txn) | |
1997 | { | |
1998 | return txn->idl; | |
1999 | } | |
2000 |