]>
Commit | Line | Data |
---|---|---|
c3bb4bd7 BP |
1 | /* Copyright (c) 2009 Nicira Networks. |
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" |
c3bb4bd7 BP |
28 | #include "json.h" |
29 | #include "jsonrpc.h" | |
30 | #include "ovsdb-data.h" | |
31 | #include "ovsdb-error.h" | |
32 | #include "ovsdb-idl-provider.h" | |
586bb84a | 33 | #include "poll-loop.h" |
c3bb4bd7 BP |
34 | #include "shash.h" |
35 | #include "util.h" | |
36 | ||
37 | #define THIS_MODULE VLM_ovsdb_idl | |
38 | #include "vlog.h" | |
39 | ||
40 | /* An arc from one idl_row to another. When row A contains a UUID that | |
41 | * references row B, this is represented by an arc from A (the source) to B | |
42 | * (the destination). | |
43 | * | |
44 | * Arcs from a row to itself are omitted, that is, src and dst are always | |
45 | * different. | |
46 | * | |
47 | * Arcs are never duplicated, that is, even if there are multiple references | |
48 | * from A to B, there is only a single arc from A to B. | |
49 | * | |
50 | * Arcs are directed: an arc from A to B is the converse of an an arc from B to | |
51 | * A. Both an arc and its converse may both be present, if each row refers | |
52 | * to the other circularly. | |
53 | * | |
54 | * The source and destination row may be in the same table or in different | |
55 | * tables. | |
56 | */ | |
57 | struct ovsdb_idl_arc { | |
58 | struct list src_node; /* In src->src_arcs list. */ | |
59 | struct list dst_node; /* In dst->dst_arcs list. */ | |
60 | struct ovsdb_idl_row *src; /* Source row. */ | |
61 | struct ovsdb_idl_row *dst; /* Destination row. */ | |
62 | }; | |
63 | ||
64 | struct ovsdb_idl { | |
115f1e4d | 65 | const struct ovsdb_idl_class *class; |
c3bb4bd7 | 66 | struct jsonrpc_session *session; |
115f1e4d BP |
67 | struct shash table_by_name; |
68 | struct ovsdb_idl_table *tables; | |
c3bb4bd7 BP |
69 | struct json *monitor_request_id; |
70 | unsigned int last_monitor_request_seqno; | |
71 | unsigned int change_seqno; | |
475281c0 BP |
72 | |
73 | /* Transaction support. */ | |
74 | struct ovsdb_idl_txn *txn; | |
75 | struct hmap outstanding_txns; | |
76 | }; | |
77 | ||
78 | struct ovsdb_idl_txn { | |
79 | struct hmap_node hmap_node; | |
80 | struct json *request_id; | |
81 | struct ovsdb_idl *idl; | |
82 | struct hmap txn_rows; | |
83 | enum ovsdb_idl_txn_status status; | |
577aebdf | 84 | bool dry_run; |
d171b584 | 85 | struct ds comment; |
b54e22e9 BP |
86 | |
87 | /* Increments. */ | |
88 | char *inc_table; | |
89 | char *inc_column; | |
90 | struct json *inc_where; | |
91 | unsigned int inc_index; | |
92 | int64_t inc_new_value; | |
c3bb4bd7 BP |
93 | }; |
94 | ||
95 | static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
96 | static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5); | |
97 | ||
98 | static void ovsdb_idl_clear(struct ovsdb_idl *); | |
99 | static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *); | |
100 | static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *); | |
101 | static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *, | |
102 | const struct json *); | |
103 | static void ovsdb_idl_process_update(struct ovsdb_idl_table *, | |
104 | const struct uuid *, | |
105 | const struct json *old, | |
106 | const struct json *new); | |
107 | static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *); | |
108 | static void ovsdb_idl_delete_row(struct ovsdb_idl_row *); | |
109 | static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *); | |
110 | ||
111 | static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *); | |
475281c0 BP |
112 | static struct ovsdb_idl_row *ovsdb_idl_row_create__( |
113 | const struct ovsdb_idl_table_class *); | |
c3bb4bd7 BP |
114 | static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *, |
115 | const struct uuid *); | |
116 | static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *); | |
117 | ||
475281c0 BP |
118 | static void ovsdb_idl_row_clear_old(struct ovsdb_idl_row *); |
119 | static void ovsdb_idl_row_clear_new(struct ovsdb_idl_row *); | |
120 | ||
121 | static void ovsdb_idl_txn_abort_all(struct ovsdb_idl *); | |
122 | static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *, | |
123 | const struct jsonrpc_msg *msg); | |
c3bb4bd7 BP |
124 | |
125 | struct ovsdb_idl * | |
126 | ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class) | |
127 | { | |
128 | struct ovsdb_idl *idl; | |
129 | size_t i; | |
130 | ||
131 | idl = xzalloc(sizeof *idl); | |
115f1e4d | 132 | idl->class = class; |
c3bb4bd7 | 133 | idl->session = jsonrpc_session_open(remote); |
115f1e4d BP |
134 | shash_init(&idl->table_by_name); |
135 | idl->tables = xmalloc(class->n_tables * sizeof *idl->tables); | |
c3bb4bd7 BP |
136 | for (i = 0; i < class->n_tables; i++) { |
137 | const struct ovsdb_idl_table_class *tc = &class->tables[i]; | |
115f1e4d | 138 | struct ovsdb_idl_table *table = &idl->tables[i]; |
c3bb4bd7 BP |
139 | size_t j; |
140 | ||
115f1e4d BP |
141 | assert(!shash_find(&idl->table_by_name, tc->name)); |
142 | shash_add(&idl->table_by_name, tc->name, table); | |
c3bb4bd7 BP |
143 | table->class = tc; |
144 | shash_init(&table->columns); | |
145 | for (j = 0; j < tc->n_columns; j++) { | |
146 | const struct ovsdb_idl_column *column = &tc->columns[j]; | |
147 | ||
148 | assert(!shash_find(&table->columns, column->name)); | |
149 | shash_add(&table->columns, column->name, column); | |
150 | } | |
151 | hmap_init(&table->rows); | |
152 | table->idl = idl; | |
153 | } | |
154 | idl->last_monitor_request_seqno = UINT_MAX; | |
475281c0 | 155 | hmap_init(&idl->outstanding_txns); |
c3bb4bd7 BP |
156 | |
157 | return idl; | |
158 | } | |
159 | ||
160 | void | |
161 | ovsdb_idl_destroy(struct ovsdb_idl *idl) | |
162 | { | |
163 | if (idl) { | |
115f1e4d | 164 | size_t i; |
c3bb4bd7 | 165 | |
475281c0 | 166 | assert(!idl->txn); |
c3bb4bd7 BP |
167 | ovsdb_idl_clear(idl); |
168 | jsonrpc_session_close(idl->session); | |
169 | ||
115f1e4d BP |
170 | for (i = 0; i < idl->class->n_tables; i++) { |
171 | struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
172 | shash_destroy(&table->columns); |
173 | hmap_destroy(&table->rows); | |
174 | } | |
115f1e4d BP |
175 | shash_destroy(&idl->table_by_name); |
176 | free(idl->tables); | |
c3bb4bd7 BP |
177 | json_destroy(idl->monitor_request_id); |
178 | free(idl); | |
179 | } | |
180 | } | |
181 | ||
182 | static void | |
183 | ovsdb_idl_clear(struct ovsdb_idl *idl) | |
184 | { | |
c3bb4bd7 | 185 | bool changed = false; |
115f1e4d | 186 | size_t i; |
c3bb4bd7 | 187 | |
115f1e4d BP |
188 | for (i = 0; i < idl->class->n_tables; i++) { |
189 | struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
190 | struct ovsdb_idl_row *row, *next_row; |
191 | ||
192 | if (hmap_is_empty(&table->rows)) { | |
193 | continue; | |
194 | } | |
195 | ||
196 | changed = true; | |
197 | HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node, | |
198 | &table->rows) { | |
199 | struct ovsdb_idl_arc *arc, *next_arc; | |
200 | ||
201 | if (!ovsdb_idl_row_is_orphan(row)) { | |
202 | (row->table->class->unparse)(row); | |
475281c0 | 203 | ovsdb_idl_row_clear_old(row); |
c3bb4bd7 BP |
204 | } |
205 | hmap_remove(&table->rows, &row->hmap_node); | |
206 | LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node, | |
207 | &row->src_arcs) { | |
208 | free(arc); | |
209 | } | |
210 | /* No need to do anything with dst_arcs: some node has those arcs | |
211 | * as forward arcs and will destroy them itself. */ | |
212 | ||
213 | free(row); | |
214 | } | |
215 | } | |
216 | ||
217 | if (changed) { | |
218 | idl->change_seqno++; | |
219 | } | |
220 | } | |
221 | ||
222 | void | |
223 | ovsdb_idl_run(struct ovsdb_idl *idl) | |
224 | { | |
225 | int i; | |
226 | ||
8bc915de | 227 | assert(!idl->txn); |
c3bb4bd7 BP |
228 | jsonrpc_session_run(idl->session); |
229 | for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) { | |
230 | struct jsonrpc_msg *msg, *reply; | |
231 | unsigned int seqno; | |
232 | ||
233 | seqno = jsonrpc_session_get_seqno(idl->session); | |
234 | if (idl->last_monitor_request_seqno != seqno) { | |
235 | idl->last_monitor_request_seqno = seqno; | |
475281c0 | 236 | ovsdb_idl_txn_abort_all(idl); |
c3bb4bd7 BP |
237 | ovsdb_idl_send_monitor_request(idl); |
238 | break; | |
239 | } | |
240 | ||
241 | msg = jsonrpc_session_recv(idl->session); | |
242 | if (!msg) { | |
243 | break; | |
244 | } | |
245 | ||
246 | reply = NULL; | |
247 | if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) { | |
248 | reply = jsonrpc_create_reply(json_clone(msg->params), msg->id); | |
249 | } else if (msg->type == JSONRPC_NOTIFY | |
250 | && !strcmp(msg->method, "update") | |
251 | && msg->params->type == JSON_ARRAY | |
252 | && msg->params->u.array.n == 2 | |
253 | && msg->params->u.array.elems[0]->type == JSON_NULL) { | |
254 | ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]); | |
255 | } else if (msg->type == JSONRPC_REPLY | |
256 | && idl->monitor_request_id | |
257 | && json_equal(idl->monitor_request_id, msg->id)) { | |
258 | json_destroy(idl->monitor_request_id); | |
259 | idl->monitor_request_id = NULL; | |
260 | ovsdb_idl_clear(idl); | |
261 | ovsdb_idl_parse_update(idl, msg->result); | |
262 | } else if (msg->type == JSONRPC_REPLY | |
263 | && msg->id && msg->id->type == JSON_STRING | |
264 | && !strcmp(msg->id->u.string, "echo")) { | |
265 | /* It's a reply to our echo request. Ignore it. */ | |
475281c0 BP |
266 | } else if ((msg->type == JSONRPC_ERROR |
267 | || msg->type == JSONRPC_REPLY) | |
268 | && ovsdb_idl_txn_process_reply(idl, msg)) { | |
269 | /* ovsdb_idl_txn_process_reply() did everything needful. */ | |
c3bb4bd7 BP |
270 | } else { |
271 | VLOG_WARN("%s: received unexpected %s message", | |
272 | jsonrpc_session_get_name(idl->session), | |
273 | jsonrpc_msg_type_to_string(msg->type)); | |
274 | jsonrpc_session_force_reconnect(idl->session); | |
275 | } | |
276 | if (reply) { | |
277 | jsonrpc_session_send(idl->session, reply); | |
278 | } | |
279 | jsonrpc_msg_destroy(msg); | |
280 | } | |
281 | } | |
282 | ||
283 | void | |
284 | ovsdb_idl_wait(struct ovsdb_idl *idl) | |
285 | { | |
286 | jsonrpc_session_wait(idl->session); | |
287 | jsonrpc_session_recv_wait(idl->session); | |
288 | } | |
289 | ||
290 | unsigned int | |
291 | ovsdb_idl_get_seqno(const struct ovsdb_idl *idl) | |
292 | { | |
293 | return idl->change_seqno; | |
294 | } | |
295 | ||
296 | void | |
297 | ovsdb_idl_force_reconnect(struct ovsdb_idl *idl) | |
298 | { | |
299 | jsonrpc_session_force_reconnect(idl->session); | |
300 | } | |
301 | \f | |
302 | static void | |
303 | ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl) | |
304 | { | |
305 | struct json *monitor_requests; | |
c3bb4bd7 | 306 | struct jsonrpc_msg *msg; |
115f1e4d | 307 | size_t i; |
c3bb4bd7 BP |
308 | |
309 | monitor_requests = json_object_create(); | |
115f1e4d BP |
310 | for (i = 0; i < idl->class->n_tables; i++) { |
311 | const struct ovsdb_idl_table *table = &idl->tables[i]; | |
c3bb4bd7 BP |
312 | const struct ovsdb_idl_table_class *tc = table->class; |
313 | struct json *monitor_request, *columns; | |
314 | size_t i; | |
315 | ||
316 | monitor_request = json_object_create(); | |
317 | columns = json_array_create_empty(); | |
318 | for (i = 0; i < tc->n_columns; i++) { | |
319 | const struct ovsdb_idl_column *column = &tc->columns[i]; | |
320 | json_array_add(columns, json_string_create(column->name)); | |
321 | } | |
322 | json_object_put(monitor_request, "columns", columns); | |
323 | json_object_put(monitor_requests, tc->name, monitor_request); | |
324 | } | |
325 | ||
326 | json_destroy(idl->monitor_request_id); | |
327 | msg = jsonrpc_create_request( | |
328 | "monitor", json_array_create_2(json_null_create(), monitor_requests), | |
329 | &idl->monitor_request_id); | |
330 | jsonrpc_session_send(idl->session, msg); | |
331 | } | |
332 | ||
333 | static void | |
334 | ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates) | |
335 | { | |
336 | struct ovsdb_error *error; | |
337 | ||
338 | idl->change_seqno++; | |
339 | ||
340 | error = ovsdb_idl_parse_update__(idl, table_updates); | |
341 | if (error) { | |
342 | if (!VLOG_DROP_WARN(&syntax_rl)) { | |
343 | char *s = ovsdb_error_to_string(error); | |
344 | VLOG_WARN_RL(&syntax_rl, "%s", s); | |
345 | free(s); | |
346 | } | |
347 | ovsdb_error_destroy(error); | |
348 | } | |
349 | } | |
350 | ||
351 | static struct ovsdb_error * | |
352 | ovsdb_idl_parse_update__(struct ovsdb_idl *idl, | |
353 | const struct json *table_updates) | |
354 | { | |
355 | const struct shash_node *tables_node; | |
356 | ||
357 | if (table_updates->type != JSON_OBJECT) { | |
358 | return ovsdb_syntax_error(table_updates, NULL, | |
359 | "<table-updates> is not an object"); | |
360 | } | |
361 | SHASH_FOR_EACH (tables_node, json_object(table_updates)) { | |
362 | const struct json *table_update = tables_node->data; | |
363 | const struct shash_node *table_node; | |
364 | struct ovsdb_idl_table *table; | |
365 | ||
115f1e4d | 366 | table = shash_find_data(&idl->table_by_name, tables_node->name); |
c3bb4bd7 BP |
367 | if (!table) { |
368 | return ovsdb_syntax_error( | |
369 | table_updates, NULL, | |
370 | "<table-updates> includes unknown table \"%s\"", | |
371 | tables_node->name); | |
372 | } | |
373 | ||
374 | if (table_update->type != JSON_OBJECT) { | |
375 | return ovsdb_syntax_error(table_update, NULL, | |
376 | "<table-update> for table \"%s\" is " | |
377 | "not an object", table->class->name); | |
378 | } | |
379 | SHASH_FOR_EACH (table_node, json_object(table_update)) { | |
380 | const struct json *row_update = table_node->data; | |
381 | const struct json *old_json, *new_json; | |
382 | struct uuid uuid; | |
383 | ||
384 | if (!uuid_from_string(&uuid, table_node->name)) { | |
385 | return ovsdb_syntax_error(table_update, NULL, | |
386 | "<table-update> for table \"%s\" " | |
387 | "contains bad UUID " | |
388 | "\"%s\" as member name", | |
389 | table->class->name, | |
390 | table_node->name); | |
391 | } | |
392 | if (row_update->type != JSON_OBJECT) { | |
393 | return ovsdb_syntax_error(row_update, NULL, | |
394 | "<table-update> for table \"%s\" " | |
395 | "contains <row-update> for %s that " | |
396 | "is not an object", | |
397 | table->class->name, | |
398 | table_node->name); | |
399 | } | |
400 | ||
401 | old_json = shash_find_data(json_object(row_update), "old"); | |
402 | new_json = shash_find_data(json_object(row_update), "new"); | |
403 | if (old_json && old_json->type != JSON_OBJECT) { | |
404 | return ovsdb_syntax_error(old_json, NULL, | |
405 | "\"old\" <row> is not object"); | |
406 | } else if (new_json && new_json->type != JSON_OBJECT) { | |
407 | return ovsdb_syntax_error(new_json, NULL, | |
408 | "\"new\" <row> is not object"); | |
409 | } else if ((old_json != NULL) + (new_json != NULL) | |
410 | != shash_count(json_object(row_update))) { | |
411 | return ovsdb_syntax_error(row_update, NULL, | |
412 | "<row-update> contains unexpected " | |
413 | "member"); | |
414 | } else if (!old_json && !new_json) { | |
415 | return ovsdb_syntax_error(row_update, NULL, | |
416 | "<row-update> missing \"old\" " | |
417 | "and \"new\" members"); | |
418 | } | |
419 | ||
420 | ovsdb_idl_process_update(table, &uuid, old_json, new_json); | |
421 | } | |
422 | } | |
423 | ||
424 | return NULL; | |
425 | } | |
426 | ||
427 | static struct ovsdb_idl_row * | |
428 | ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid) | |
429 | { | |
430 | struct ovsdb_idl_row *row; | |
431 | ||
432 | HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node, | |
433 | uuid_hash(uuid), &table->rows) { | |
434 | if (uuid_equals(&row->uuid, uuid)) { | |
435 | return row; | |
436 | } | |
437 | } | |
438 | return NULL; | |
439 | } | |
440 | ||
441 | static void | |
442 | ovsdb_idl_process_update(struct ovsdb_idl_table *table, | |
443 | const struct uuid *uuid, const struct json *old, | |
444 | const struct json *new) | |
445 | { | |
446 | struct ovsdb_idl_row *row; | |
447 | ||
448 | row = ovsdb_idl_get_row(table, uuid); | |
449 | if (!new) { | |
450 | /* Delete row. */ | |
451 | if (row && !ovsdb_idl_row_is_orphan(row)) { | |
452 | /* XXX perhaps we should check the 'old' values? */ | |
453 | ovsdb_idl_delete_row(row); | |
454 | } else { | |
455 | VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" " | |
456 | "from table %s", | |
457 | UUID_ARGS(uuid), table->class->name); | |
458 | } | |
459 | } else if (!old) { | |
460 | /* Insert row. */ | |
461 | if (!row) { | |
462 | ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); | |
463 | } else if (ovsdb_idl_row_is_orphan(row)) { | |
464 | ovsdb_idl_insert_row(row, new); | |
465 | } else { | |
466 | VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to " | |
467 | "table %s", UUID_ARGS(uuid), table->class->name); | |
468 | ovsdb_idl_modify_row(row, new); | |
469 | } | |
470 | } else { | |
471 | /* Modify row. */ | |
472 | if (row) { | |
473 | /* XXX perhaps we should check the 'old' values? */ | |
474 | if (!ovsdb_idl_row_is_orphan(row)) { | |
475 | ovsdb_idl_modify_row(row, new); | |
476 | } else { | |
477 | VLOG_WARN_RL(&semantic_rl, "cannot modify missing but " | |
478 | "referenced row "UUID_FMT" in table %s", | |
479 | UUID_ARGS(uuid), table->class->name); | |
480 | ovsdb_idl_insert_row(row, new); | |
481 | } | |
482 | } else { | |
483 | VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" " | |
484 | "in table %s", UUID_ARGS(uuid), table->class->name); | |
485 | ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new); | |
486 | } | |
487 | } | |
488 | } | |
489 | ||
490 | static void | |
491 | ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json) | |
492 | { | |
493 | struct ovsdb_idl_table *table = row->table; | |
494 | struct shash_node *node; | |
495 | ||
496 | SHASH_FOR_EACH (node, json_object(row_json)) { | |
497 | const char *column_name = node->name; | |
498 | const struct ovsdb_idl_column *column; | |
499 | struct ovsdb_datum datum; | |
500 | struct ovsdb_error *error; | |
501 | ||
502 | column = shash_find_data(&table->columns, column_name); | |
503 | if (!column) { | |
504 | VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT, | |
505 | column_name, UUID_ARGS(&row->uuid)); | |
506 | continue; | |
507 | } | |
508 | ||
509 | error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL); | |
510 | if (!error) { | |
475281c0 | 511 | ovsdb_datum_swap(&row->old[column - table->class->columns], |
c3bb4bd7 BP |
512 | &datum); |
513 | ovsdb_datum_destroy(&datum, &column->type); | |
514 | } else { | |
515 | char *s = ovsdb_error_to_string(error); | |
516 | VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT | |
517 | " in table %s: %s", column_name, | |
518 | UUID_ARGS(&row->uuid), table->class->name, s); | |
519 | free(s); | |
520 | ovsdb_error_destroy(error); | |
521 | } | |
522 | } | |
523 | } | |
524 | ||
525 | static bool | |
526 | ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row) | |
527 | { | |
475281c0 | 528 | return !row->old; |
c3bb4bd7 BP |
529 | } |
530 | ||
531 | static void | |
475281c0 | 532 | ovsdb_idl_row_clear_old(struct ovsdb_idl_row *row) |
c3bb4bd7 | 533 | { |
475281c0 | 534 | assert(row->old == row->new); |
c3bb4bd7 BP |
535 | if (!ovsdb_idl_row_is_orphan(row)) { |
536 | const struct ovsdb_idl_table_class *class = row->table->class; | |
537 | size_t i; | |
538 | ||
539 | for (i = 0; i < class->n_columns; i++) { | |
475281c0 BP |
540 | ovsdb_datum_destroy(&row->old[i], &class->columns[i].type); |
541 | } | |
542 | free(row->old); | |
543 | row->old = row->new = NULL; | |
544 | } | |
545 | } | |
546 | ||
547 | static void | |
548 | ovsdb_idl_row_clear_new(struct ovsdb_idl_row *row) | |
549 | { | |
550 | if (row->old != row->new) { | |
551 | if (row->new) { | |
552 | const struct ovsdb_idl_table_class *class = row->table->class; | |
553 | size_t i; | |
554 | ||
555 | BITMAP_FOR_EACH_1 (i, class->n_columns, row->written) { | |
556 | ovsdb_datum_destroy(&row->new[i], &class->columns[i].type); | |
557 | } | |
558 | free(row->new); | |
559 | free(row->written); | |
560 | row->written = NULL; | |
c3bb4bd7 | 561 | } |
475281c0 | 562 | row->new = row->old; |
c3bb4bd7 BP |
563 | } |
564 | } | |
565 | ||
566 | static void | |
567 | ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts) | |
568 | { | |
569 | struct ovsdb_idl_arc *arc, *next; | |
570 | ||
571 | /* Delete all forward arcs. If 'destroy_dsts', destroy any orphaned rows | |
572 | * that this causes to be unreferenced. */ | |
573 | LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node, | |
574 | &row->src_arcs) { | |
575 | list_remove(&arc->dst_node); | |
576 | if (destroy_dsts | |
577 | && ovsdb_idl_row_is_orphan(arc->dst) | |
578 | && list_is_empty(&arc->dst->dst_arcs)) { | |
579 | ovsdb_idl_row_destroy(arc->dst); | |
580 | } | |
581 | free(arc); | |
582 | } | |
583 | list_init(&row->src_arcs); | |
584 | } | |
585 | ||
586 | /* Force nodes that reference 'row' to reparse. */ | |
587 | static void | |
588 | ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row, bool destroy_dsts) | |
589 | { | |
590 | struct ovsdb_idl_arc *arc, *next; | |
591 | ||
592 | /* This is trickier than it looks. ovsdb_idl_row_clear_arcs() will destroy | |
593 | * 'arc', so we need to use the "safe" variant of list traversal. However, | |
594 | * calling ref->table->class->parse will add an arc equivalent to 'arc' to | |
595 | * row->arcs. That could be a problem for traversal, but it adds it at the | |
596 | * beginning of the list to prevent us from stumbling upon it again. | |
597 | * | |
598 | * (If duplicate arcs were possible then we would need to make sure that | |
599 | * 'next' didn't also point into 'arc''s destination, but we forbid | |
600 | * duplicate arcs.) */ | |
601 | LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node, | |
602 | &row->dst_arcs) { | |
603 | struct ovsdb_idl_row *ref = arc->src; | |
604 | ||
605 | (ref->table->class->unparse)(ref); | |
606 | ovsdb_idl_row_clear_arcs(ref, destroy_dsts); | |
607 | (ref->table->class->parse)(ref); | |
608 | } | |
609 | } | |
610 | ||
475281c0 BP |
611 | static struct ovsdb_idl_row * |
612 | ovsdb_idl_row_create__(const struct ovsdb_idl_table_class *class) | |
613 | { | |
72c6edc5 | 614 | struct ovsdb_idl_row *row = xzalloc(class->allocation_size); |
475281c0 BP |
615 | memset(row, 0, sizeof *row); |
616 | list_init(&row->src_arcs); | |
617 | list_init(&row->dst_arcs); | |
618 | hmap_node_nullify(&row->txn_node); | |
619 | return row; | |
620 | } | |
621 | ||
c3bb4bd7 BP |
622 | static struct ovsdb_idl_row * |
623 | ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid) | |
624 | { | |
475281c0 | 625 | struct ovsdb_idl_row *row = ovsdb_idl_row_create__(table->class); |
c3bb4bd7 BP |
626 | hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid)); |
627 | row->uuid = *uuid; | |
c3bb4bd7 | 628 | row->table = table; |
c3bb4bd7 BP |
629 | return row; |
630 | } | |
631 | ||
632 | static void | |
633 | ovsdb_idl_row_destroy(struct ovsdb_idl_row *row) | |
634 | { | |
635 | if (row) { | |
475281c0 | 636 | ovsdb_idl_row_clear_old(row); |
c3bb4bd7 BP |
637 | hmap_remove(&row->table->rows, &row->hmap_node); |
638 | free(row); | |
639 | } | |
640 | } | |
641 | ||
642 | static void | |
643 | ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json) | |
644 | { | |
645 | const struct ovsdb_idl_table_class *class = row->table->class; | |
646 | size_t i; | |
647 | ||
475281c0 BP |
648 | assert(!row->old && !row->new); |
649 | row->old = row->new = xmalloc(class->n_columns * sizeof *row->old); | |
c3bb4bd7 | 650 | for (i = 0; i < class->n_columns; i++) { |
475281c0 | 651 | ovsdb_datum_init_default(&row->old[i], &class->columns[i].type); |
c3bb4bd7 BP |
652 | } |
653 | ovsdb_idl_row_update(row, row_json); | |
654 | (class->parse)(row); | |
655 | ||
656 | ovsdb_idl_row_reparse_backrefs(row, false); | |
657 | } | |
658 | ||
659 | static void | |
660 | ovsdb_idl_delete_row(struct ovsdb_idl_row *row) | |
661 | { | |
662 | (row->table->class->unparse)(row); | |
663 | ovsdb_idl_row_clear_arcs(row, true); | |
475281c0 | 664 | ovsdb_idl_row_clear_old(row); |
c3bb4bd7 BP |
665 | if (list_is_empty(&row->dst_arcs)) { |
666 | ovsdb_idl_row_destroy(row); | |
667 | } else { | |
668 | ovsdb_idl_row_reparse_backrefs(row, true); | |
669 | } | |
670 | } | |
671 | ||
672 | static void | |
673 | ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json) | |
674 | { | |
675 | (row->table->class->unparse)(row); | |
676 | ovsdb_idl_row_clear_arcs(row, true); | |
677 | ovsdb_idl_row_update(row, row_json); | |
678 | (row->table->class->parse)(row); | |
679 | } | |
680 | ||
681 | static bool | |
682 | may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst) | |
683 | { | |
684 | const struct ovsdb_idl_arc *arc; | |
685 | ||
686 | /* No self-arcs. */ | |
687 | if (src == dst) { | |
688 | return false; | |
689 | } | |
690 | ||
691 | /* No duplicate arcs. | |
692 | * | |
693 | * We only need to test whether the first arc in dst->dst_arcs originates | |
694 | * at 'src', since we add all of the arcs from a given source in a clump | |
695 | * (in a single call to a row's ->parse function) and new arcs are always | |
696 | * added at the front of the dst_arcs list. */ | |
697 | if (list_is_empty(&dst->dst_arcs)) { | |
698 | return true; | |
699 | } | |
700 | arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node); | |
701 | return arc->src != src; | |
702 | } | |
703 | ||
115f1e4d | 704 | static struct ovsdb_idl_table * |
475281c0 BP |
705 | ovsdb_idl_table_from_class(const struct ovsdb_idl *idl, |
706 | const struct ovsdb_idl_table_class *table_class) | |
115f1e4d BP |
707 | { |
708 | return &idl->tables[table_class - idl->class->tables]; | |
709 | } | |
710 | ||
c3bb4bd7 BP |
711 | struct ovsdb_idl_row * |
712 | ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src, | |
713 | struct ovsdb_idl_table_class *dst_table_class, | |
714 | const struct uuid *dst_uuid) | |
715 | { | |
716 | struct ovsdb_idl *idl = src->table->idl; | |
e9011ac8 | 717 | struct ovsdb_idl_table *dst_table; |
c3bb4bd7 BP |
718 | struct ovsdb_idl_arc *arc; |
719 | struct ovsdb_idl_row *dst; | |
720 | ||
475281c0 | 721 | dst_table = ovsdb_idl_table_from_class(idl, dst_table_class); |
e9011ac8 | 722 | dst = ovsdb_idl_get_row(dst_table, dst_uuid); |
c3bb4bd7 | 723 | if (!dst) { |
c3bb4bd7 BP |
724 | dst = ovsdb_idl_row_create(dst_table, dst_uuid); |
725 | } | |
726 | ||
727 | /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */ | |
728 | if (may_add_arc(src, dst)) { | |
729 | /* The arc *must* be added at the front of the dst_arcs list. See | |
730 | * ovsdb_idl_row_reparse_backrefs() for details. */ | |
731 | arc = xmalloc(sizeof *arc); | |
732 | list_push_front(&src->src_arcs, &arc->src_node); | |
733 | list_push_front(&dst->dst_arcs, &arc->dst_node); | |
734 | arc->src = src; | |
735 | arc->dst = dst; | |
736 | } | |
737 | ||
738 | return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL; | |
739 | } | |
740 | ||
741 | static struct ovsdb_idl_row * | |
742 | next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node) | |
743 | { | |
744 | for (; node; node = hmap_next(&table->rows, node)) { | |
745 | struct ovsdb_idl_row *row; | |
746 | ||
747 | row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node); | |
748 | if (!ovsdb_idl_row_is_orphan(row)) { | |
749 | return row; | |
750 | } | |
751 | } | |
752 | return NULL; | |
753 | } | |
754 | ||
755 | struct ovsdb_idl_row * | |
756 | ovsdb_idl_first_row(const struct ovsdb_idl *idl, | |
757 | const struct ovsdb_idl_table_class *table_class) | |
758 | { | |
475281c0 BP |
759 | struct ovsdb_idl_table *table |
760 | = ovsdb_idl_table_from_class(idl, table_class); | |
c3bb4bd7 BP |
761 | return next_real_row(table, hmap_first(&table->rows)); |
762 | } | |
763 | ||
764 | struct ovsdb_idl_row * | |
765 | ovsdb_idl_next_row(const struct ovsdb_idl_row *row) | |
766 | { | |
767 | struct ovsdb_idl_table *table = row->table; | |
768 | ||
769 | return next_real_row(table, hmap_next(&table->rows, &row->hmap_node)); | |
770 | } | |
475281c0 BP |
771 | \f |
772 | /* Transactions. */ | |
773 | ||
774 | static void ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn, | |
775 | enum ovsdb_idl_txn_status); | |
776 | ||
777 | const char * | |
778 | ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status) | |
779 | { | |
780 | switch (status) { | |
b54e22e9 BP |
781 | case TXN_UNCHANGED: |
782 | return "unchanged"; | |
475281c0 BP |
783 | case TXN_INCOMPLETE: |
784 | return "incomplete"; | |
785 | case TXN_ABORTED: | |
786 | return "aborted"; | |
787 | case TXN_SUCCESS: | |
788 | return "success"; | |
789 | case TXN_TRY_AGAIN: | |
790 | return "try again"; | |
791 | case TXN_ERROR: | |
792 | return "error"; | |
793 | } | |
794 | return "<unknown>"; | |
795 | } | |
796 | ||
797 | struct ovsdb_idl_txn * | |
798 | ovsdb_idl_txn_create(struct ovsdb_idl *idl) | |
799 | { | |
800 | struct ovsdb_idl_txn *txn; | |
801 | ||
802 | assert(!idl->txn); | |
803 | idl->txn = txn = xmalloc(sizeof *txn); | |
804 | txn->idl = idl; | |
805 | txn->status = TXN_INCOMPLETE; | |
806 | hmap_init(&txn->txn_rows); | |
577aebdf | 807 | txn->dry_run = false; |
d171b584 | 808 | ds_init(&txn->comment); |
b54e22e9 BP |
809 | txn->inc_table = NULL; |
810 | txn->inc_column = NULL; | |
811 | txn->inc_where = NULL; | |
475281c0 BP |
812 | return txn; |
813 | } | |
814 | ||
d171b584 BP |
815 | void |
816 | ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *txn, const char *s) | |
817 | { | |
818 | if (txn->comment.length) { | |
819 | ds_put_char(&txn->comment, '\n'); | |
820 | } | |
821 | ds_put_cstr(&txn->comment, s); | |
822 | } | |
823 | ||
577aebdf BP |
824 | void |
825 | ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *txn) | |
826 | { | |
827 | txn->dry_run = true; | |
828 | } | |
829 | ||
b54e22e9 BP |
830 | void |
831 | ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn, const char *table, | |
832 | const char *column, const struct json *where) | |
833 | { | |
834 | assert(!txn->inc_table); | |
835 | txn->inc_table = xstrdup(table); | |
836 | txn->inc_column = xstrdup(column); | |
837 | txn->inc_where = where ? json_clone(where) : json_array_create_empty(); | |
838 | } | |
839 | ||
475281c0 BP |
840 | void |
841 | ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn) | |
842 | { | |
fbd8fd40 BP |
843 | if (txn->status == TXN_INCOMPLETE) { |
844 | hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node); | |
845 | } | |
475281c0 | 846 | ovsdb_idl_txn_abort(txn); |
d171b584 | 847 | ds_destroy(&txn->comment); |
b54e22e9 BP |
848 | free(txn->inc_table); |
849 | free(txn->inc_column); | |
850 | json_destroy(txn->inc_where); | |
475281c0 BP |
851 | free(txn); |
852 | } | |
853 | ||
586bb84a BP |
854 | void |
855 | ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *txn) | |
856 | { | |
857 | if (txn->status != TXN_INCOMPLETE) { | |
858 | poll_immediate_wake(); | |
859 | } | |
860 | } | |
861 | ||
475281c0 BP |
862 | static struct json * |
863 | where_uuid_equals(const struct uuid *uuid) | |
864 | { | |
865 | return | |
866 | json_array_create_1( | |
867 | json_array_create_3( | |
868 | json_string_create("_uuid"), | |
869 | json_string_create("=="), | |
870 | json_array_create_2( | |
871 | json_string_create("uuid"), | |
872 | json_string_create_nocopy( | |
873 | xasprintf(UUID_FMT, UUID_ARGS(uuid)))))); | |
874 | } | |
875 | ||
876 | static char * | |
877 | uuid_name_from_uuid(const struct uuid *uuid) | |
878 | { | |
879 | char *name; | |
880 | char *p; | |
881 | ||
882 | name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid)); | |
883 | for (p = name; *p != '\0'; p++) { | |
884 | if (*p == '-') { | |
885 | *p = '_'; | |
886 | } | |
887 | } | |
888 | ||
889 | return name; | |
890 | } | |
891 | ||
892 | static const struct ovsdb_idl_row * | |
893 | ovsdb_idl_txn_get_row(const struct ovsdb_idl_txn *txn, const struct uuid *uuid) | |
894 | { | |
895 | const struct ovsdb_idl_row *row; | |
896 | ||
897 | HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, txn_node, | |
898 | uuid_hash(uuid), &txn->txn_rows) { | |
899 | if (uuid_equals(&row->uuid, uuid)) { | |
900 | return row; | |
901 | } | |
902 | } | |
903 | return NULL; | |
904 | } | |
905 | ||
906 | /* XXX there must be a cleaner way to do this */ | |
907 | static struct json * | |
908 | substitute_uuids(struct json *json, const struct ovsdb_idl_txn *txn) | |
909 | { | |
910 | if (json->type == JSON_ARRAY) { | |
911 | struct uuid uuid; | |
912 | size_t i; | |
913 | ||
914 | if (json->u.array.n == 2 | |
915 | && json->u.array.elems[0]->type == JSON_STRING | |
916 | && json->u.array.elems[1]->type == JSON_STRING | |
917 | && !strcmp(json->u.array.elems[0]->u.string, "uuid") | |
918 | && uuid_from_string(&uuid, json->u.array.elems[1]->u.string)) { | |
919 | const struct ovsdb_idl_row *row; | |
920 | ||
921 | row = ovsdb_idl_txn_get_row(txn, &uuid); | |
922 | if (row && !row->old && row->new) { | |
923 | json_destroy(json); | |
924 | ||
925 | return json_array_create_2( | |
926 | json_string_create("named-uuid"), | |
927 | json_string_create_nocopy(uuid_name_from_uuid(&uuid))); | |
928 | } | |
929 | } | |
930 | ||
931 | for (i = 0; i < json->u.array.n; i++) { | |
932 | json->u.array.elems[i] = substitute_uuids(json->u.array.elems[i], | |
933 | txn); | |
934 | } | |
935 | } else if (json->type == JSON_OBJECT) { | |
936 | struct shash_node *node; | |
937 | ||
938 | SHASH_FOR_EACH (node, json_object(json)) { | |
939 | node->data = substitute_uuids(node->data, txn); | |
940 | } | |
941 | } | |
942 | return json; | |
943 | } | |
944 | ||
945 | static void | |
946 | ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn) | |
947 | { | |
948 | struct ovsdb_idl_row *row, *next; | |
949 | ||
950 | HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_idl_row, txn_node, | |
951 | &txn->txn_rows) { | |
8bc915de BP |
952 | if (row->old && row->written) { |
953 | (row->table->class->unparse)(row); | |
954 | ovsdb_idl_row_clear_arcs(row, false); | |
955 | (row->table->class->parse)(row); | |
956 | } | |
475281c0 BP |
957 | ovsdb_idl_row_clear_new(row); |
958 | ||
959 | free(row->prereqs); | |
960 | row->prereqs = NULL; | |
961 | ||
962 | free(row->written); | |
963 | row->written = NULL; | |
964 | ||
965 | hmap_remove(&txn->txn_rows, &row->txn_node); | |
966 | hmap_node_nullify(&row->txn_node); | |
967 | } | |
968 | hmap_destroy(&txn->txn_rows); | |
969 | hmap_init(&txn->txn_rows); | |
970 | } | |
971 | ||
972 | enum ovsdb_idl_txn_status | |
973 | ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn) | |
974 | { | |
975 | struct ovsdb_idl_row *row; | |
976 | struct json *operations; | |
977 | bool any_updates; | |
475281c0 BP |
978 | |
979 | if (txn != txn->idl->txn) { | |
980 | return txn->status; | |
981 | } | |
982 | ||
983 | operations = json_array_create_empty(); | |
984 | ||
985 | /* Add prerequisites and declarations of new rows. */ | |
986 | HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) { | |
987 | /* XXX check that deleted rows exist even if no prereqs? */ | |
988 | if (row->prereqs) { | |
989 | const struct ovsdb_idl_table_class *class = row->table->class; | |
990 | size_t n_columns = class->n_columns; | |
991 | struct json *op, *columns, *row_json; | |
992 | size_t idx; | |
993 | ||
994 | op = json_object_create(); | |
995 | json_array_add(operations, op); | |
996 | json_object_put_string(op, "op", "wait"); | |
997 | json_object_put_string(op, "table", class->name); | |
998 | json_object_put(op, "timeout", json_integer_create(0)); | |
999 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1000 | json_object_put_string(op, "until", "=="); | |
1001 | columns = json_array_create_empty(); | |
1002 | json_object_put(op, "columns", columns); | |
1003 | row_json = json_object_create(); | |
1004 | json_object_put(op, "rows", json_array_create_1(row_json)); | |
1005 | ||
1006 | BITMAP_FOR_EACH_1 (idx, n_columns, row->prereqs) { | |
1007 | const struct ovsdb_idl_column *column = &class->columns[idx]; | |
1008 | json_array_add(columns, json_string_create(column->name)); | |
1009 | json_object_put(row_json, column->name, | |
1010 | ovsdb_datum_to_json(&row->old[idx], | |
1011 | &column->type)); | |
1012 | } | |
1013 | } | |
1014 | if (row->new && !row->old) { | |
1015 | struct json *op; | |
1016 | ||
1017 | op = json_object_create(); | |
1018 | json_array_add(operations, op); | |
1019 | json_object_put_string(op, "op", "declare"); | |
1020 | json_object_put(op, "uuid-name", | |
1021 | json_string_create_nocopy( | |
1022 | uuid_name_from_uuid(&row->uuid))); | |
1023 | } | |
1024 | } | |
1025 | ||
1026 | /* Add updates. */ | |
1027 | any_updates = false; | |
1028 | HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) { | |
1029 | const struct ovsdb_idl_table_class *class = row->table->class; | |
475281c0 BP |
1030 | |
1031 | if (row->old == row->new) { | |
1032 | continue; | |
1033 | } else if (!row->new) { | |
1034 | struct json *op = json_object_create(); | |
1035 | json_object_put_string(op, "op", "delete"); | |
1036 | json_object_put_string(op, "table", class->name); | |
1037 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1038 | json_array_add(operations, op); | |
f0f54cb4 | 1039 | any_updates = true; |
475281c0 | 1040 | } else { |
f0f54cb4 BP |
1041 | struct json *row_json; |
1042 | struct json *op; | |
1043 | size_t idx; | |
1044 | ||
1045 | op = json_object_create(); | |
1046 | json_object_put_string(op, "op", row->old ? "update" : "insert"); | |
1047 | json_object_put_string(op, "table", class->name); | |
1048 | if (row->old) { | |
1049 | json_object_put(op, "where", where_uuid_equals(&row->uuid)); | |
1050 | } else { | |
1051 | json_object_put(op, "uuid-name", | |
1052 | json_string_create_nocopy( | |
1053 | uuid_name_from_uuid(&row->uuid))); | |
1054 | } | |
1055 | row_json = json_object_create(); | |
1056 | json_object_put(op, "row", row_json); | |
1057 | ||
1058 | BITMAP_FOR_EACH_1 (idx, class->n_columns, row->written) { | |
475281c0 BP |
1059 | const struct ovsdb_idl_column *column = &class->columns[idx]; |
1060 | ||
f0f54cb4 BP |
1061 | if (!row->old || !ovsdb_datum_equals(&row->old[idx], |
1062 | &row->new[idx], | |
1063 | &column->type)) { | |
1064 | json_object_put(row_json, column->name, | |
1065 | substitute_uuids( | |
1066 | ovsdb_datum_to_json(&row->new[idx], | |
1067 | &column->type), | |
1068 | txn)); | |
475281c0 | 1069 | } |
f0f54cb4 BP |
1070 | } |
1071 | ||
1072 | if (!row->old || !shash_is_empty(json_object(row_json))) { | |
1073 | json_array_add(operations, op); | |
1074 | any_updates = true; | |
1075 | } else { | |
1076 | json_destroy(op); | |
475281c0 BP |
1077 | } |
1078 | } | |
1079 | } | |
1080 | ||
b54e22e9 BP |
1081 | /* Add increment. */ |
1082 | if (txn->inc_table && any_updates) { | |
1083 | struct json *op; | |
1084 | ||
1085 | txn->inc_index = operations->u.array.n; | |
1086 | ||
1087 | op = json_object_create(); | |
1088 | json_object_put_string(op, "op", "mutate"); | |
1089 | json_object_put_string(op, "table", txn->inc_table); | |
1090 | json_object_put(op, "where", | |
1091 | substitute_uuids(json_clone(txn->inc_where), txn)); | |
1092 | json_object_put(op, "mutations", | |
1093 | json_array_create_1( | |
1094 | json_array_create_3( | |
1095 | json_string_create(txn->inc_column), | |
1096 | json_string_create("+="), | |
1097 | json_integer_create(1)))); | |
1098 | json_array_add(operations, op); | |
1099 | ||
1100 | op = json_object_create(); | |
1101 | json_object_put_string(op, "op", "select"); | |
1102 | json_object_put_string(op, "table", txn->inc_table); | |
1103 | json_object_put(op, "where", | |
1104 | substitute_uuids(json_clone(txn->inc_where), txn)); | |
1105 | json_object_put(op, "columns", | |
1106 | json_array_create_1(json_string_create( | |
1107 | txn->inc_column))); | |
1108 | json_array_add(operations, op); | |
1109 | } | |
1110 | ||
d171b584 BP |
1111 | if (txn->comment.length) { |
1112 | struct json *op = json_object_create(); | |
1113 | json_object_put_string(op, "op", "comment"); | |
1114 | json_object_put_string(op, "comment", ds_cstr(&txn->comment)); | |
1115 | json_array_add(operations, op); | |
1116 | } | |
1117 | ||
577aebdf BP |
1118 | if (txn->dry_run) { |
1119 | struct json *op = json_object_create(); | |
1120 | json_object_put_string(op, "op", "abort"); | |
1121 | json_array_add(operations, op); | |
1122 | } | |
1123 | ||
72c6edc5 | 1124 | if (!any_updates) { |
b54e22e9 | 1125 | txn->status = TXN_UNCHANGED; |
72c6edc5 BP |
1126 | } else if (!jsonrpc_session_send( |
1127 | txn->idl->session, | |
1128 | jsonrpc_create_request( | |
1129 | "transact", operations, &txn->request_id))) { | |
1130 | hmap_insert(&txn->idl->outstanding_txns, &txn->hmap_node, | |
1131 | json_hash(txn->request_id, 0)); | |
1132 | } else { | |
1133 | txn->status = TXN_INCOMPLETE; | |
1134 | } | |
475281c0 | 1135 | |
475281c0 | 1136 | txn->idl->txn = NULL; |
475281c0 | 1137 | ovsdb_idl_txn_disassemble(txn); |
475281c0 BP |
1138 | return txn->status; |
1139 | } | |
1140 | ||
b54e22e9 BP |
1141 | int64_t |
1142 | ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn) | |
1143 | { | |
1144 | assert(txn->status == TXN_SUCCESS); | |
1145 | return txn->inc_new_value; | |
1146 | } | |
1147 | ||
475281c0 BP |
1148 | void |
1149 | ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn) | |
1150 | { | |
1151 | ovsdb_idl_txn_disassemble(txn); | |
1152 | if (txn->status == TXN_INCOMPLETE) { | |
1153 | txn->status = TXN_ABORTED; | |
1154 | } | |
1155 | } | |
1156 | ||
1157 | static void | |
1158 | ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn, | |
1159 | enum ovsdb_idl_txn_status status) | |
1160 | { | |
1161 | txn->status = status; | |
1162 | hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node); | |
1163 | } | |
1164 | ||
1165 | void | |
1166 | ovsdb_idl_txn_write(struct ovsdb_idl_row *row, | |
1167 | const struct ovsdb_idl_column *column, | |
1168 | struct ovsdb_datum *datum) | |
1169 | { | |
1170 | const struct ovsdb_idl_table_class *class = row->table->class; | |
1171 | size_t column_idx = column - class->columns; | |
1172 | ||
1173 | assert(row->new); | |
1174 | if (hmap_node_is_null(&row->txn_node)) { | |
1175 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1176 | uuid_hash(&row->uuid)); | |
1177 | } | |
1178 | if (row->old == row->new) { | |
1179 | row->new = xmalloc(class->n_columns * sizeof *row->new); | |
1180 | } | |
1181 | if (!row->written) { | |
1182 | row->written = bitmap_allocate(class->n_columns); | |
1183 | } | |
1184 | if (bitmap_is_set(row->written, column_idx)) { | |
1185 | ovsdb_datum_destroy(&row->new[column_idx], &column->type); | |
1186 | } else { | |
1187 | bitmap_set1(row->written, column_idx); | |
1188 | } | |
1189 | row->new[column_idx] = *datum; | |
1190 | } | |
1191 | ||
1192 | void | |
1193 | ovsdb_idl_txn_verify(const struct ovsdb_idl_row *row_, | |
1194 | const struct ovsdb_idl_column *column) | |
1195 | { | |
1196 | struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_; | |
1197 | const struct ovsdb_idl_table_class *class = row->table->class; | |
1198 | size_t column_idx = column - class->columns; | |
1199 | ||
1200 | assert(row->new); | |
1201 | if (!row->old | |
1202 | || (row->written && bitmap_is_set(row->written, column_idx))) { | |
1203 | return; | |
1204 | } | |
1205 | ||
1206 | if (hmap_node_is_null(&row->txn_node)) { | |
1207 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1208 | uuid_hash(&row->uuid)); | |
1209 | } | |
1210 | if (!row->prereqs) { | |
1211 | row->prereqs = bitmap_allocate(class->n_columns); | |
1212 | } | |
1213 | bitmap_set1(row->prereqs, column_idx); | |
1214 | } | |
1215 | ||
1216 | void | |
1217 | ovsdb_idl_txn_delete(struct ovsdb_idl_row *row) | |
1218 | { | |
1219 | assert(row->new); | |
1220 | if (!row->old) { | |
1221 | ovsdb_idl_row_clear_new(row); | |
1222 | assert(!row->prereqs); | |
1223 | hmap_remove(&row->table->idl->txn->txn_rows, &row->txn_node); | |
1224 | free(row); | |
1225 | } | |
1226 | if (hmap_node_is_null(&row->txn_node)) { | |
1227 | hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node, | |
1228 | uuid_hash(&row->uuid)); | |
1229 | } | |
1ebeed63 BP |
1230 | ovsdb_idl_row_clear_new(row); |
1231 | row->new = NULL; | |
475281c0 BP |
1232 | } |
1233 | ||
1234 | struct ovsdb_idl_row * | |
1235 | ovsdb_idl_txn_insert(struct ovsdb_idl_txn *txn, | |
1236 | const struct ovsdb_idl_table_class *class) | |
1237 | { | |
1238 | struct ovsdb_idl_row *row = ovsdb_idl_row_create__(class); | |
1239 | uuid_generate(&row->uuid); | |
1240 | row->table = ovsdb_idl_table_from_class(txn->idl, class); | |
1241 | row->new = xmalloc(class->n_columns * sizeof *row->new); | |
1242 | row->written = bitmap_allocate(class->n_columns); | |
1243 | hmap_insert(&txn->txn_rows, &row->txn_node, uuid_hash(&row->uuid)); | |
1244 | return row; | |
1245 | } | |
1246 | ||
1247 | static void | |
1248 | ovsdb_idl_txn_abort_all(struct ovsdb_idl *idl) | |
1249 | { | |
1250 | struct ovsdb_idl_txn *txn; | |
1251 | ||
1252 | HMAP_FOR_EACH (txn, struct ovsdb_idl_txn, hmap_node, | |
1253 | &idl->outstanding_txns) { | |
1254 | ovsdb_idl_txn_complete(txn, TXN_TRY_AGAIN); | |
1255 | } | |
1256 | } | |
1257 | ||
1258 | static struct ovsdb_idl_txn * | |
1259 | ovsdb_idl_txn_find(struct ovsdb_idl *idl, const struct json *id) | |
1260 | { | |
1261 | struct ovsdb_idl_txn *txn; | |
1262 | ||
1263 | HMAP_FOR_EACH_WITH_HASH (txn, struct ovsdb_idl_txn, hmap_node, | |
1264 | json_hash(id, 0), &idl->outstanding_txns) { | |
1265 | if (json_equal(id, txn->request_id)) { | |
1266 | return txn; | |
1267 | } | |
1268 | } | |
1269 | return NULL; | |
1270 | } | |
1271 | ||
b54e22e9 BP |
1272 | static bool |
1273 | check_json_type(const struct json *json, enum json_type type, const char *name) | |
1274 | { | |
1275 | if (!json) { | |
1276 | VLOG_WARN_RL(&syntax_rl, "%s is missing", name); | |
1277 | return false; | |
1278 | } else if (json->type != type) { | |
1279 | VLOG_WARN_RL(&syntax_rl, "%s is %s instead of %s", | |
1280 | name, json_type_to_string(json->type), | |
1281 | json_type_to_string(type)); | |
1282 | return false; | |
1283 | } else { | |
1284 | return true; | |
1285 | } | |
1286 | } | |
1287 | ||
1288 | static bool | |
1289 | ovsdb_idl_txn_process_inc_reply(struct ovsdb_idl_txn *txn, | |
1290 | const struct json_array *results) | |
1291 | { | |
1292 | struct json *count, *rows, *row, *column; | |
1293 | struct shash *mutate, *select; | |
1294 | ||
1295 | if (txn->inc_index + 2 > results->n) { | |
1296 | VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations " | |
1297 | "for increment (has %u, needs %u)", | |
1298 | results->n, txn->inc_index + 2); | |
1299 | return false; | |
1300 | } | |
1301 | ||
1302 | /* We know that this is a JSON objects because the loop in | |
1303 | * ovsdb_idl_txn_process_reply() checked. */ | |
1304 | mutate = json_object(results->elems[txn->inc_index]); | |
1305 | count = shash_find_data(mutate, "count"); | |
1306 | if (!check_json_type(count, JSON_INTEGER, "\"mutate\" reply \"count\"")) { | |
1307 | return false; | |
1308 | } | |
1309 | if (count->u.integer != 1) { | |
1310 | VLOG_WARN_RL(&syntax_rl, | |
1311 | "\"mutate\" reply \"count\" is %"PRId64" instead of 1", | |
1312 | count->u.integer); | |
1313 | return false; | |
1314 | } | |
1315 | ||
1316 | select = json_object(results->elems[txn->inc_index + 1]); | |
1317 | rows = shash_find_data(select, "rows"); | |
1318 | if (!check_json_type(rows, JSON_ARRAY, "\"select\" reply \"rows\"")) { | |
1319 | return false; | |
1320 | } | |
1321 | if (rows->u.array.n != 1) { | |
1322 | VLOG_WARN_RL(&syntax_rl, "\"select\" reply \"rows\" has %u elements " | |
1323 | "instead of 1", | |
1324 | rows->u.array.n); | |
1325 | return false; | |
1326 | } | |
1327 | row = rows->u.array.elems[0]; | |
1328 | if (!check_json_type(row, JSON_OBJECT, "\"select\" reply row")) { | |
1329 | return false; | |
1330 | } | |
1331 | column = shash_find_data(json_object(row), txn->inc_column); | |
1332 | if (!check_json_type(column, JSON_INTEGER, | |
1333 | "\"select\" reply inc column")) { | |
1334 | return false; | |
1335 | } | |
1336 | txn->inc_new_value = column->u.integer; | |
1337 | return true; | |
1338 | } | |
1339 | ||
1340 | ||
475281c0 BP |
1341 | static bool |
1342 | ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl, | |
1343 | const struct jsonrpc_msg *msg) | |
1344 | { | |
1345 | struct ovsdb_idl_txn *txn; | |
1346 | enum ovsdb_idl_txn_status status; | |
1347 | ||
1348 | txn = ovsdb_idl_txn_find(idl, msg->id); | |
1349 | if (!txn) { | |
1350 | return false; | |
1351 | } | |
1352 | ||
1353 | if (msg->type == JSONRPC_ERROR) { | |
1354 | status = TXN_ERROR; | |
1355 | } else if (msg->result->type != JSON_ARRAY) { | |
1356 | VLOG_WARN_RL(&syntax_rl, "reply to \"transact\" is not JSON array"); | |
1357 | status = TXN_ERROR; | |
1358 | } else { | |
1359 | int hard_errors = 0; | |
1360 | int soft_errors = 0; | |
1361 | size_t i; | |
1362 | ||
1363 | for (i = 0; i < msg->result->u.array.n; i++) { | |
1364 | struct json *json = msg->result->u.array.elems[i]; | |
1365 | ||
1366 | if (json->type == JSON_NULL) { | |
1367 | /* This isn't an error in itself but indicates that some prior | |
1368 | * operation failed, so make sure that we know about it. */ | |
1369 | soft_errors++; | |
1370 | } else if (json->type == JSON_OBJECT) { | |
1371 | struct json *error; | |
1372 | ||
1373 | error = shash_find_data(json_object(json), "error"); | |
1374 | if (error) { | |
1375 | if (error->type == JSON_STRING) { | |
1376 | if (!strcmp(error->u.string, "timed out")) { | |
1377 | soft_errors++; | |
577aebdf | 1378 | } else if (strcmp(error->u.string, "aborted")) { |
475281c0 BP |
1379 | hard_errors++; |
1380 | } | |
1381 | } else { | |
1382 | hard_errors++; | |
1383 | VLOG_WARN_RL(&syntax_rl, | |
1384 | "\"error\" in reply is not JSON string"); | |
1385 | } | |
1386 | } | |
1387 | } else { | |
1388 | hard_errors++; | |
1389 | VLOG_WARN_RL(&syntax_rl, | |
1390 | "operation reply is not JSON null or object"); | |
1391 | } | |
1392 | } | |
1393 | ||
b54e22e9 BP |
1394 | if (txn->inc_table |
1395 | && !soft_errors | |
1396 | && !hard_errors | |
1397 | && !ovsdb_idl_txn_process_inc_reply(txn, | |
1398 | json_array(msg->result))) { | |
1399 | hard_errors++; | |
1400 | } | |
1401 | ||
475281c0 BP |
1402 | status = (hard_errors ? TXN_ERROR |
1403 | : soft_errors ? TXN_TRY_AGAIN | |
1404 | : TXN_SUCCESS); | |
1405 | } | |
1406 | ||
1407 | ovsdb_idl_txn_complete(txn, status); | |
1408 | return true; | |
1409 | } | |
76c91af9 BP |
1410 | |
1411 | struct ovsdb_idl_txn * | |
1412 | ovsdb_idl_txn_get(const struct ovsdb_idl_row *row) | |
1413 | { | |
1414 | struct ovsdb_idl_txn *txn = row->table->idl->txn; | |
1415 | assert(txn != NULL); | |
1416 | return txn; | |
1417 | } |