]>
Commit | Line | Data |
---|---|---|
71c93bd4 | 1 | /* Copyright (c) 2009, 2010 Nicira Networks |
f85f8ebb 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 "transaction.h" | |
19 | ||
20 | #include <assert.h> | |
21 | ||
17d18afb | 22 | #include "bitmap.h" |
d171b584 | 23 | #include "dynamic-string.h" |
f85f8ebb BP |
24 | #include "hash.h" |
25 | #include "hmap.h" | |
26 | #include "json.h" | |
71c93bd4 | 27 | #include "list.h" |
f85f8ebb BP |
28 | #include "ovsdb-error.h" |
29 | #include "ovsdb.h" | |
30 | #include "row.h" | |
31 | #include "table.h" | |
32 | #include "uuid.h" | |
33 | ||
34 | struct ovsdb_txn { | |
35 | struct ovsdb *db; | |
71c93bd4 | 36 | struct list txn_tables; /* Contains "struct ovsdb_txn_table"s. */ |
d171b584 | 37 | struct ds comment; |
f85f8ebb BP |
38 | }; |
39 | ||
40 | /* A table modified by a transaction. */ | |
41 | struct ovsdb_txn_table { | |
71c93bd4 | 42 | struct list node; /* Element in ovsdb_txn's txn_tables list. */ |
f85f8ebb BP |
43 | struct ovsdb_table *table; |
44 | struct hmap txn_rows; /* Contains "struct ovsdb_txn_row"s. */ | |
3e010b7a BP |
45 | |
46 | /* Used by for_each_txn_row(). */ | |
47 | unsigned int serial; /* Serial number of in-progress iteration. */ | |
48 | unsigned int n_processed; /* Number of rows processed. */ | |
f85f8ebb BP |
49 | }; |
50 | ||
51 | /* A row modified by the transaction: | |
52 | * | |
53 | * - A row added by a transaction will have null 'old' and non-null 'new'. | |
54 | * | |
55 | * - A row deleted by a transaction will have non-null 'old' and null | |
56 | * 'new'. | |
57 | * | |
58 | * - A row modified by a transaction will have non-null 'old' and 'new'. | |
59 | * | |
60 | * - 'old' and 'new' both null is invalid. It would indicate that a row | |
61 | * was added then deleted within a single transaction, but we instead | |
62 | * handle that case by deleting the txn_row entirely. | |
63 | */ | |
64 | struct ovsdb_txn_row { | |
65 | struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */ | |
66 | struct ovsdb_row *old; /* The old row. */ | |
67 | struct ovsdb_row *new; /* The new row. */ | |
c7d85e0d | 68 | size_t n_refs; /* Number of remaining references. */ |
3e010b7a BP |
69 | |
70 | /* Used by for_each_txn_row(). */ | |
71 | unsigned int serial; /* Serial number of in-progress commit. */ | |
17d18afb BP |
72 | |
73 | unsigned long changed[]; /* Bits set to 1 for columns that changed. */ | |
f85f8ebb BP |
74 | }; |
75 | ||
3e010b7a BP |
76 | static void ovsdb_txn_row_prefree(struct ovsdb_txn_row *); |
77 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
78 | for_each_txn_row(struct ovsdb_txn *txn, | |
79 | struct ovsdb_error *(*)(struct ovsdb_txn *, | |
80 | struct ovsdb_txn_row *)); | |
81 | ||
82 | /* Used by for_each_txn_row() to track tables and rows that have been | |
83 | * processed. */ | |
84 | static unsigned int serial; | |
85 | ||
f85f8ebb BP |
86 | struct ovsdb_txn * |
87 | ovsdb_txn_create(struct ovsdb *db) | |
88 | { | |
89 | struct ovsdb_txn *txn = xmalloc(sizeof *txn); | |
90 | txn->db = db; | |
71c93bd4 | 91 | list_init(&txn->txn_tables); |
d171b584 | 92 | ds_init(&txn->comment); |
f85f8ebb BP |
93 | return txn; |
94 | } | |
95 | ||
96 | static void | |
3e010b7a | 97 | ovsdb_txn_free(struct ovsdb_txn *txn) |
f85f8ebb | 98 | { |
3e010b7a | 99 | assert(list_is_empty(&txn->txn_tables)); |
d171b584 | 100 | ds_destroy(&txn->comment); |
f85f8ebb BP |
101 | free(txn); |
102 | } | |
103 | ||
17d18afb | 104 | static struct ovsdb_error * |
3e010b7a BP |
105 | ovsdb_txn_row_abort(struct ovsdb_txn *txn OVS_UNUSED, |
106 | struct ovsdb_txn_row *txn_row) | |
f85f8ebb BP |
107 | { |
108 | struct ovsdb_row *old = txn_row->old; | |
109 | struct ovsdb_row *new = txn_row->new; | |
110 | ||
3e010b7a | 111 | ovsdb_txn_row_prefree(txn_row); |
f85f8ebb BP |
112 | if (!old) { |
113 | hmap_remove(&new->table->rows, &new->hmap_node); | |
114 | } else if (!new) { | |
115 | hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old)); | |
116 | } else { | |
117 | hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node); | |
118 | } | |
119 | ovsdb_row_destroy(new); | |
3e010b7a BP |
120 | free(txn_row); |
121 | ||
122 | return NULL; | |
f85f8ebb BP |
123 | } |
124 | ||
125 | void | |
126 | ovsdb_txn_abort(struct ovsdb_txn *txn) | |
127 | { | |
3e010b7a BP |
128 | ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_abort)); |
129 | ovsdb_txn_free(txn); | |
f85f8ebb BP |
130 | } |
131 | ||
0d0f05b9 BP |
132 | static struct ovsdb_txn_row * |
133 | find_txn_row(const struct ovsdb_table *table, const struct uuid *uuid) | |
134 | { | |
135 | struct ovsdb_txn_row *txn_row; | |
136 | ||
137 | if (!table->txn_table) { | |
138 | return NULL; | |
139 | } | |
140 | ||
141 | HMAP_FOR_EACH_WITH_HASH (txn_row, struct ovsdb_txn_row, hmap_node, | |
142 | uuid_hash(uuid), &table->txn_table->txn_rows) { | |
143 | const struct ovsdb_row *row; | |
144 | ||
145 | row = txn_row->old ? txn_row->old : txn_row->new; | |
146 | if (uuid_equals(uuid, ovsdb_row_get_uuid(row))) { | |
147 | return txn_row; | |
148 | } | |
149 | } | |
150 | ||
151 | return NULL; | |
152 | } | |
153 | ||
c7d85e0d | 154 | static struct ovsdb_error * WARN_UNUSED_RESULT |
97f7803b BP |
155 | ovsdb_txn_adjust_atom_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r, |
156 | const struct ovsdb_column *c, | |
c7d85e0d BP |
157 | const struct ovsdb_base_type *base, |
158 | const union ovsdb_atom *atoms, unsigned int n, | |
159 | int delta) | |
0d0f05b9 | 160 | { |
c7d85e0d | 161 | const struct ovsdb_table *table; |
0d0f05b9 BP |
162 | unsigned int i; |
163 | ||
7360012b | 164 | if (!ovsdb_base_type_is_strong_ref(base)) { |
c7d85e0d BP |
165 | return NULL; |
166 | } | |
167 | ||
168 | table = base->u.uuid.refTable; | |
0d0f05b9 BP |
169 | for (i = 0; i < n; i++) { |
170 | const struct uuid *uuid = &atoms[i].uuid; | |
171 | struct ovsdb_txn_row *txn_row = find_txn_row(table, uuid); | |
c7d85e0d BP |
172 | if (!txn_row) { |
173 | const struct ovsdb_row *row = ovsdb_table_get_row(table, uuid); | |
174 | if (row) { | |
175 | txn_row = ovsdb_txn_row_modify(txn, row)->txn_row; | |
0d0f05b9 | 176 | } else { |
c7d85e0d | 177 | return ovsdb_error("referential integrity violation", |
97f7803b BP |
178 | "Table %s column %s row "UUID_FMT" " |
179 | "references nonexistent row "UUID_FMT" in " | |
180 | "table %s.", | |
181 | r->table->schema->name, c->name, | |
182 | UUID_ARGS(ovsdb_row_get_uuid(r)), | |
183 | UUID_ARGS(uuid), table->schema->name); | |
0d0f05b9 BP |
184 | } |
185 | } | |
c7d85e0d | 186 | txn_row->n_refs += delta; |
0d0f05b9 | 187 | } |
c7d85e0d BP |
188 | |
189 | return NULL; | |
0d0f05b9 BP |
190 | } |
191 | ||
c7d85e0d BP |
192 | static struct ovsdb_error * WARN_UNUSED_RESULT |
193 | ovsdb_txn_adjust_row_refs(struct ovsdb_txn *txn, const struct ovsdb_row *r, | |
194 | const struct ovsdb_column *column, int delta) | |
0d0f05b9 BP |
195 | { |
196 | const struct ovsdb_datum *field = &r->fields[column->index]; | |
c7d85e0d | 197 | struct ovsdb_error *error; |
0d0f05b9 | 198 | |
97f7803b | 199 | error = ovsdb_txn_adjust_atom_refs(txn, r, column, &column->type.key, |
c7d85e0d BP |
200 | field->keys, field->n, delta); |
201 | if (!error) { | |
97f7803b | 202 | error = ovsdb_txn_adjust_atom_refs(txn, r, column, &column->type.value, |
c7d85e0d | 203 | field->values, field->n, delta); |
0d0f05b9 | 204 | } |
c7d85e0d | 205 | return error; |
0d0f05b9 BP |
206 | } |
207 | ||
208 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
c7d85e0d | 209 | update_row_ref_count(struct ovsdb_txn *txn, struct ovsdb_txn_row *r) |
0d0f05b9 | 210 | { |
c7d85e0d BP |
211 | struct ovsdb_table *table = r->old ? r->old->table : r->new->table; |
212 | struct shash_node *node; | |
213 | ||
214 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
215 | const struct ovsdb_column *column = node->data; | |
216 | struct ovsdb_error *error; | |
217 | ||
218 | if (r->old) { | |
219 | error = ovsdb_txn_adjust_row_refs(txn, r->old, column, -1); | |
220 | if (error) { | |
221 | ovsdb_error_destroy(error); | |
222 | return OVSDB_BUG("error decreasing refcount"); | |
223 | } | |
224 | } | |
225 | if (r->new) { | |
226 | error = ovsdb_txn_adjust_row_refs(txn, r->new, column, 1); | |
227 | if (error) { | |
228 | return error; | |
0d0f05b9 BP |
229 | } |
230 | } | |
231 | } | |
0d0f05b9 | 232 | |
c7d85e0d | 233 | return NULL; |
0d0f05b9 BP |
234 | } |
235 | ||
236 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
c7d85e0d | 237 | check_ref_count(struct ovsdb_txn *txn OVS_UNUSED, struct ovsdb_txn_row *r) |
0d0f05b9 | 238 | { |
c7d85e0d BP |
239 | if (r->new || !r->n_refs) { |
240 | return NULL; | |
241 | } else { | |
242 | return ovsdb_error("referential integrity violation", | |
243 | "cannot delete %s row "UUID_FMT" because " | |
244 | "of %zu remaining reference(s)", | |
245 | r->old->table->schema->name, | |
246 | UUID_ARGS(ovsdb_row_get_uuid(r->old)), | |
247 | r->n_refs); | |
0d0f05b9 | 248 | } |
0d0f05b9 BP |
249 | } |
250 | ||
251 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
252 | update_ref_counts(struct ovsdb_txn *txn) | |
253 | { | |
254 | struct ovsdb_error *error; | |
0d0f05b9 | 255 | |
c7d85e0d | 256 | error = for_each_txn_row(txn, update_row_ref_count); |
0d0f05b9 BP |
257 | if (error) { |
258 | return error; | |
259 | } | |
260 | ||
c7d85e0d | 261 | return for_each_txn_row(txn, check_ref_count); |
0d0f05b9 BP |
262 | } |
263 | ||
17d18afb | 264 | static struct ovsdb_error * |
3e010b7a BP |
265 | ovsdb_txn_row_commit(struct ovsdb_txn *txn OVS_UNUSED, |
266 | struct ovsdb_txn_row *txn_row) | |
267 | { | |
268 | ovsdb_txn_row_prefree(txn_row); | |
c7d85e0d BP |
269 | if (txn_row->new) { |
270 | txn_row->new->n_refs = txn_row->n_refs; | |
271 | } | |
3e010b7a BP |
272 | ovsdb_row_destroy(txn_row->old); |
273 | free(txn_row); | |
274 | ||
275 | return NULL; | |
276 | } | |
277 | ||
7360012b BP |
278 | static void |
279 | add_weak_ref(struct ovsdb_txn *txn, | |
280 | const struct ovsdb_row *src_, const struct ovsdb_row *dst_) | |
281 | { | |
282 | struct ovsdb_row *src = (struct ovsdb_row *) src_; | |
283 | struct ovsdb_row *dst = (struct ovsdb_row *) dst_; | |
284 | struct ovsdb_weak_ref *weak; | |
285 | ||
286 | if (src == dst) { | |
287 | return; | |
288 | } | |
289 | ||
290 | dst = ovsdb_txn_row_modify(txn, dst); | |
291 | ||
292 | if (!list_is_empty(&dst->dst_refs)) { | |
293 | /* Omit duplicates. */ | |
294 | weak = CONTAINER_OF(list_back(&dst->dst_refs), | |
295 | struct ovsdb_weak_ref, dst_node); | |
296 | if (weak->src == src) { | |
297 | return; | |
298 | } | |
299 | } | |
300 | ||
301 | weak = xmalloc(sizeof *weak); | |
302 | weak->src = src; | |
303 | list_push_back(&dst->dst_refs, &weak->dst_node); | |
304 | list_push_back(&src->src_refs, &weak->src_node); | |
305 | } | |
306 | ||
307 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
308 | assess_weak_refs(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) | |
309 | { | |
310 | struct ovsdb_table *table; | |
311 | struct shash_node *node; | |
312 | ||
313 | if (txn_row->old) { | |
314 | /* Mark rows that have weak references to 'txn_row' as modified, so | |
315 | * that their weak references will get reassessed. */ | |
316 | struct ovsdb_weak_ref *weak, *next; | |
317 | ||
318 | LIST_FOR_EACH_SAFE (weak, next, struct ovsdb_weak_ref, dst_node, | |
319 | &txn_row->old->dst_refs) { | |
320 | if (!weak->src->txn_row) { | |
321 | ovsdb_txn_row_modify(txn, weak->src); | |
322 | } | |
323 | } | |
324 | } | |
325 | ||
326 | if (!txn_row->new) { | |
327 | /* We don't have to do anything about references that originate at | |
328 | * 'txn_row', because ovsdb_row_destroy() will remove those weak | |
329 | * references. */ | |
330 | return NULL; | |
331 | } | |
332 | ||
333 | table = txn_row->new->table; | |
334 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
335 | const struct ovsdb_column *column = node->data; | |
336 | struct ovsdb_datum *datum = &txn_row->new->fields[column->index]; | |
337 | unsigned int orig_n, i; | |
338 | bool zero = false; | |
339 | ||
340 | orig_n = datum->n; | |
341 | ||
342 | if (ovsdb_base_type_is_weak_ref(&column->type.key)) { | |
343 | for (i = 0; i < datum->n; ) { | |
344 | const struct ovsdb_row *row; | |
345 | ||
346 | row = ovsdb_table_get_row(column->type.key.u.uuid.refTable, | |
347 | &datum->keys[i].uuid); | |
348 | if (row) { | |
349 | add_weak_ref(txn, txn_row->new, row); | |
350 | i++; | |
351 | } else { | |
352 | if (uuid_is_zero(&datum->keys[i].uuid)) { | |
353 | zero = true; | |
354 | } | |
355 | ovsdb_datum_remove_unsafe(datum, i, &column->type); | |
356 | } | |
357 | } | |
358 | } | |
359 | ||
360 | if (ovsdb_base_type_is_weak_ref(&column->type.value)) { | |
361 | for (i = 0; i < datum->n; ) { | |
362 | const struct ovsdb_row *row; | |
363 | ||
364 | row = ovsdb_table_get_row(column->type.value.u.uuid.refTable, | |
365 | &datum->values[i].uuid); | |
366 | if (row) { | |
367 | add_weak_ref(txn, txn_row->new, row); | |
368 | i++; | |
369 | } else { | |
370 | if (uuid_is_zero(&datum->values[i].uuid)) { | |
371 | zero = true; | |
372 | } | |
373 | ovsdb_datum_remove_unsafe(datum, i, &column->type); | |
374 | } | |
375 | } | |
376 | } | |
377 | ||
378 | if (datum->n != orig_n) { | |
379 | bitmap_set1(txn_row->changed, column->index); | |
380 | ovsdb_datum_sort_assert(datum, column->type.key.type); | |
381 | if (datum->n < column->type.n_min) { | |
382 | const struct uuid *row_uuid = ovsdb_row_get_uuid(txn_row->new); | |
383 | if (zero && !txn_row->old) { | |
384 | return ovsdb_error( | |
385 | "constraint violation", | |
386 | "Weak reference column \"%s\" in \"%s\" row "UUID_FMT | |
387 | " (inserted within this transaction) contained " | |
388 | "all-zeros UUID (probably as the default value for " | |
389 | "this column) but deleting this value caused a " | |
390 | "constraint volation because this column is not " | |
391 | "allowed to be empty.", column->name, | |
392 | table->schema->name, UUID_ARGS(row_uuid)); | |
393 | } else { | |
394 | return ovsdb_error( | |
395 | "constraint violation", | |
396 | "Deletion of %u weak reference(s) to deleted (or " | |
397 | "never-existing) rows from column \"%s\" in \"%s\" " | |
398 | "row "UUID_FMT" %scaused this column to become empty, " | |
399 | "but constraints on this column disallow an " | |
400 | "empty column.", | |
401 | orig_n - datum->n, column->name, table->schema->name, | |
402 | UUID_ARGS(row_uuid), | |
403 | (txn_row->old | |
404 | ? "" | |
405 | : "(inserted within this transaction) ")); | |
406 | } | |
407 | } | |
408 | } | |
409 | } | |
410 | ||
411 | return NULL; | |
412 | } | |
413 | ||
17d18afb BP |
414 | static struct ovsdb_error * WARN_UNUSED_RESULT |
415 | determine_changes(struct ovsdb_txn *txn, struct ovsdb_txn_row *txn_row) | |
416 | { | |
417 | struct ovsdb_table *table; | |
418 | ||
419 | table = (txn_row->old ? txn_row->old : txn_row->new)->table; | |
420 | if (txn_row->old && txn_row->new) { | |
421 | struct shash_node *node; | |
422 | bool changed = false; | |
423 | ||
424 | SHASH_FOR_EACH (node, &table->schema->columns) { | |
425 | const struct ovsdb_column *column = node->data; | |
426 | const struct ovsdb_type *type = &column->type; | |
427 | unsigned int idx = column->index; | |
428 | ||
429 | if (!ovsdb_datum_equals(&txn_row->old->fields[idx], | |
430 | &txn_row->new->fields[idx], | |
431 | type)) { | |
432 | bitmap_set1(txn_row->changed, idx); | |
433 | changed = true; | |
434 | } | |
435 | } | |
436 | ||
437 | if (!changed) { | |
438 | /* Nothing actually changed in this row, so drop it. */ | |
439 | ovsdb_txn_row_abort(txn, txn_row); | |
440 | } | |
441 | } else { | |
442 | bitmap_set_multiple(txn_row->changed, 0, | |
443 | shash_count(&table->schema->columns), 1); | |
444 | } | |
445 | ||
446 | return NULL; | |
447 | } | |
448 | ||
87ab878c BP |
449 | static struct ovsdb_error * WARN_UNUSED_RESULT |
450 | check_max_rows(struct ovsdb_txn *txn) | |
451 | { | |
452 | struct ovsdb_txn_table *t; | |
453 | ||
454 | LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) { | |
455 | size_t n_rows = hmap_count(&t->table->rows); | |
456 | unsigned int max_rows = t->table->schema->max_rows; | |
457 | ||
458 | if (n_rows > max_rows) { | |
459 | return ovsdb_error("constraint violation", | |
460 | "transaction causes \"%s\" table to contain " | |
461 | "%zu rows, greater than the schema-defined " | |
462 | "limit of %u row(s)", | |
463 | t->table->schema->name, n_rows, max_rows); | |
464 | } | |
465 | } | |
466 | ||
467 | return NULL; | |
468 | } | |
469 | ||
bd06962a BP |
470 | struct ovsdb_error * |
471 | ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable) | |
f85f8ebb | 472 | { |
bd06962a BP |
473 | struct ovsdb_replica *replica; |
474 | struct ovsdb_error *error; | |
f85f8ebb | 475 | |
17d18afb BP |
476 | /* Figure out what actually changed, and abort early if the transaction |
477 | * was really a no-op. */ | |
478 | error = for_each_txn_row(txn, determine_changes); | |
479 | if (error) { | |
480 | ovsdb_error_destroy(error); | |
481 | return OVSDB_BUG("can't happen"); | |
482 | } | |
483 | if (list_is_empty(&txn->txn_tables)) { | |
484 | ovsdb_txn_abort(txn); | |
485 | return NULL; | |
486 | } | |
487 | ||
87ab878c BP |
488 | /* Check maximum rows table constraints. */ |
489 | error = check_max_rows(txn); | |
490 | if (error) { | |
491 | ovsdb_txn_abort(txn); | |
492 | return error; | |
493 | } | |
494 | ||
17d18afb | 495 | /* Update reference counts and check referential integrity. */ |
0d0f05b9 BP |
496 | error = update_ref_counts(txn); |
497 | if (error) { | |
498 | ovsdb_txn_abort(txn); | |
499 | return error; | |
500 | } | |
501 | ||
7360012b BP |
502 | /* Check reference counts and remove bad reference for "weak" referential |
503 | * integrity. */ | |
504 | error = for_each_txn_row(txn, assess_weak_refs); | |
505 | if (error) { | |
506 | ovsdb_txn_abort(txn); | |
507 | return error; | |
508 | } | |
509 | ||
17d18afb | 510 | /* Send the commit to each replica. */ |
bd06962a BP |
511 | LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) { |
512 | error = (replica->class->commit)(replica, txn, durable); | |
f85f8ebb | 513 | if (error) { |
bd06962a BP |
514 | /* We don't support two-phase commit so only the first replica is |
515 | * allowed to report an error. */ | |
516 | assert(&replica->node == txn->db->replicas.next); | |
f85f8ebb | 517 | |
bd06962a | 518 | ovsdb_txn_abort(txn); |
f85f8ebb BP |
519 | return error; |
520 | } | |
521 | } | |
522 | ||
3e010b7a | 523 | /* Finalize commit. */ |
bd06962a | 524 | txn->db->run_triggers = true; |
3e010b7a BP |
525 | ovsdb_error_assert(for_each_txn_row(txn, ovsdb_txn_row_commit)); |
526 | ovsdb_txn_free(txn); | |
527 | ||
f85f8ebb BP |
528 | return NULL; |
529 | } | |
530 | ||
bd06962a BP |
531 | void |
532 | ovsdb_txn_for_each_change(const struct ovsdb_txn *txn, | |
533 | ovsdb_txn_row_cb_func *cb, void *aux) | |
f85f8ebb | 534 | { |
bd06962a BP |
535 | struct ovsdb_txn_table *t; |
536 | struct ovsdb_txn_row *r; | |
f85f8ebb | 537 | |
71c93bd4 | 538 | LIST_FOR_EACH (t, struct ovsdb_txn_table, node, &txn->txn_tables) { |
bd06962a | 539 | HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) { |
17d18afb | 540 | if (!cb(r->old, r->new, r->changed, aux)) { |
bd06962a BP |
541 | break; |
542 | } | |
f85f8ebb | 543 | } |
bd06962a | 544 | } |
f85f8ebb BP |
545 | } |
546 | ||
547 | static struct ovsdb_txn_table * | |
71c93bd4 | 548 | ovsdb_txn_create_txn_table(struct ovsdb_txn *txn, struct ovsdb_table *table) |
f85f8ebb | 549 | { |
71c93bd4 BP |
550 | if (!table->txn_table) { |
551 | struct ovsdb_txn_table *txn_table; | |
f85f8ebb | 552 | |
71c93bd4 | 553 | table->txn_table = txn_table = xmalloc(sizeof *table->txn_table); |
f85f8ebb BP |
554 | txn_table->table = table; |
555 | hmap_init(&txn_table->txn_rows); | |
3e010b7a | 556 | txn_table->serial = serial - 1; |
71c93bd4 | 557 | list_push_back(&txn->txn_tables, &txn_table->node); |
f85f8ebb | 558 | } |
71c93bd4 | 559 | return table->txn_table; |
f85f8ebb BP |
560 | } |
561 | ||
562 | static struct ovsdb_txn_row * | |
71c93bd4 | 563 | ovsdb_txn_row_create(struct ovsdb_txn *txn, struct ovsdb_table *table, |
b405dcfb | 564 | const struct ovsdb_row *old_, struct ovsdb_row *new) |
f85f8ebb | 565 | { |
b405dcfb | 566 | struct ovsdb_row *old = (struct ovsdb_row *) old_; |
17d18afb | 567 | size_t n_columns = shash_count(&table->schema->columns); |
71c93bd4 | 568 | struct ovsdb_txn_table *txn_table; |
f85f8ebb BP |
569 | struct ovsdb_txn_row *txn_row; |
570 | ||
17d18afb BP |
571 | txn_row = xzalloc(offsetof(struct ovsdb_txn_row, changed) |
572 | + bitmap_n_bytes(n_columns)); | |
573 | txn_row->old = (struct ovsdb_row *) old; | |
f85f8ebb | 574 | txn_row->new = new; |
c7d85e0d | 575 | txn_row->n_refs = old ? old->n_refs : 0; |
3e010b7a | 576 | txn_row->serial = serial - 1; |
71c93bd4 | 577 | |
b405dcfb BP |
578 | if (old) { |
579 | old->txn_row = txn_row; | |
580 | } | |
581 | if (new) { | |
582 | new->txn_row = txn_row; | |
583 | } | |
584 | ||
71c93bd4 BP |
585 | txn_table = ovsdb_txn_create_txn_table(txn, table); |
586 | hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, | |
587 | ovsdb_row_hash(old ? old : new)); | |
f85f8ebb BP |
588 | |
589 | return txn_row; | |
590 | } | |
591 | ||
592 | struct ovsdb_row * | |
593 | ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_) | |
594 | { | |
595 | struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_; | |
596 | ||
597 | if (ro_row->txn_row) { | |
598 | assert(ro_row == ro_row->txn_row->new); | |
599 | return ro_row; | |
600 | } else { | |
601 | struct ovsdb_table *table = ro_row->table; | |
f85f8ebb BP |
602 | struct ovsdb_row *rw_row; |
603 | ||
f85f8ebb | 604 | rw_row = ovsdb_row_clone(ro_row); |
0d0f05b9 | 605 | rw_row->n_refs = ro_row->n_refs; |
f85f8ebb | 606 | uuid_generate(ovsdb_row_get_version_rw(rw_row)); |
b405dcfb | 607 | ovsdb_txn_row_create(txn, table, ro_row, rw_row); |
f85f8ebb BP |
608 | hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node); |
609 | ||
610 | return rw_row; | |
611 | } | |
612 | } | |
613 | ||
614 | void | |
615 | ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row) | |
616 | { | |
617 | uint32_t hash = ovsdb_row_hash(row); | |
618 | struct ovsdb_table *table = row->table; | |
f85f8ebb BP |
619 | |
620 | uuid_generate(ovsdb_row_get_version_rw(row)); | |
621 | ||
b405dcfb | 622 | ovsdb_txn_row_create(txn, table, NULL, row); |
f85f8ebb BP |
623 | hmap_insert(&table->rows, &row->hmap_node, hash); |
624 | } | |
625 | ||
626 | /* 'row' must be assumed destroyed upon return; the caller must not reference | |
627 | * it again. */ | |
628 | void | |
629 | ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_) | |
630 | { | |
631 | struct ovsdb_row *row = (struct ovsdb_row *) row_; | |
632 | struct ovsdb_table *table = row->table; | |
633 | struct ovsdb_txn_row *txn_row = row->txn_row; | |
f85f8ebb BP |
634 | |
635 | hmap_remove(&table->rows, &row->hmap_node); | |
636 | ||
637 | if (!txn_row) { | |
b405dcfb | 638 | ovsdb_txn_row_create(txn, table, row, NULL); |
f85f8ebb BP |
639 | } else { |
640 | assert(txn_row->new == row); | |
641 | if (txn_row->old) { | |
642 | txn_row->new = NULL; | |
643 | } else { | |
71c93bd4 | 644 | hmap_remove(&table->txn_table->txn_rows, &txn_row->hmap_node); |
e084f690 | 645 | free(txn_row); |
f85f8ebb BP |
646 | } |
647 | ovsdb_row_destroy(row); | |
648 | } | |
649 | } | |
d171b584 BP |
650 | |
651 | void | |
652 | ovsdb_txn_add_comment(struct ovsdb_txn *txn, const char *s) | |
653 | { | |
654 | if (txn->comment.length) { | |
655 | ds_put_char(&txn->comment, '\n'); | |
656 | } | |
657 | ds_put_cstr(&txn->comment, s); | |
658 | } | |
659 | ||
660 | const char * | |
661 | ovsdb_txn_get_comment(const struct ovsdb_txn *txn) | |
662 | { | |
663 | return txn->comment.length ? ds_cstr_ro(&txn->comment) : NULL; | |
664 | } | |
3e010b7a BP |
665 | \f |
666 | static void | |
667 | ovsdb_txn_row_prefree(struct ovsdb_txn_row *txn_row) | |
668 | { | |
669 | struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new; | |
670 | struct ovsdb_txn_table *txn_table = row->table->txn_table; | |
671 | ||
672 | txn_table->n_processed--; | |
673 | hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node); | |
674 | ||
675 | if (txn_row->old) { | |
676 | txn_row->old->txn_row = NULL; | |
677 | } | |
678 | if (txn_row->new) { | |
679 | txn_row->new->txn_row = NULL; | |
680 | } | |
681 | } | |
682 | ||
683 | static void | |
684 | ovsdb_txn_table_destroy(struct ovsdb_txn_table *txn_table) | |
685 | { | |
686 | assert(hmap_is_empty(&txn_table->txn_rows)); | |
687 | txn_table->table->txn_table = NULL; | |
688 | hmap_destroy(&txn_table->txn_rows); | |
689 | list_remove(&txn_table->node); | |
690 | free(txn_table); | |
691 | } | |
692 | ||
693 | /* Calls 'cb' for every txn_row within 'txn'. If 'cb' returns nonnull, this | |
694 | * aborts the iteration and for_each_txn_row() passes the error up. Otherwise, | |
695 | * returns a null pointer after iteration is complete. | |
696 | * | |
697 | * 'cb' may insert new txn_rows and new txn_tables into 'txn'. It may delete | |
698 | * the txn_row that it is passed in, or txn_rows in txn_tables other than the | |
699 | * one passed to 'cb'. It may *not* delete txn_rows other than the one passed | |
700 | * in within the same txn_table. It may *not* delete any txn_tables. As long | |
701 | * as these rules are followed, 'cb' will be called exactly once for each | |
702 | * txn_row in 'txn', even those added by 'cb'. | |
703 | */ | |
704 | static struct ovsdb_error * WARN_UNUSED_RESULT | |
705 | for_each_txn_row(struct ovsdb_txn *txn, | |
706 | struct ovsdb_error *(*cb)(struct ovsdb_txn *, | |
707 | struct ovsdb_txn_row *)) | |
708 | { | |
709 | bool any_work; | |
710 | ||
711 | serial++; | |
712 | ||
713 | do { | |
714 | struct ovsdb_txn_table *t, *next_txn_table; | |
715 | ||
716 | any_work = false; | |
717 | LIST_FOR_EACH_SAFE (t, next_txn_table, struct ovsdb_txn_table, node, | |
718 | &txn->txn_tables) { | |
719 | if (t->serial != serial) { | |
720 | t->serial = serial; | |
721 | t->n_processed = 0; | |
722 | } | |
723 | ||
724 | while (t->n_processed < hmap_count(&t->txn_rows)) { | |
725 | struct ovsdb_txn_row *r, *next_txn_row; | |
726 | ||
727 | HMAP_FOR_EACH_SAFE (r, next_txn_row, | |
728 | struct ovsdb_txn_row, hmap_node, | |
729 | &t->txn_rows) { | |
730 | if (r->serial != serial) { | |
731 | struct ovsdb_error *error; | |
732 | ||
733 | r->serial = serial; | |
734 | t->n_processed++; | |
735 | any_work = true; | |
736 | ||
737 | error = cb(txn, r); | |
738 | if (error) { | |
739 | return error; | |
740 | } | |
741 | } | |
742 | } | |
743 | } | |
744 | if (hmap_is_empty(&t->txn_rows)) { | |
745 | /* Table is empty. Drop it. */ | |
746 | ovsdb_txn_table_destroy(t); | |
747 | } | |
748 | } | |
749 | } while (any_work); | |
750 | ||
751 | return NULL; | |
752 | } |