]>
Commit | Line | Data |
---|---|---|
ac0630a2 RB |
1 | /* |
2 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | * you may not use this file except in compliance with the License. | |
4 | * You may obtain a copy of the License at: | |
5 | * | |
6 | * http://www.apache.org/licenses/LICENSE-2.0 | |
7 | * | |
8 | * Unless required by applicable law or agreed to in writing, software | |
9 | * distributed under the License is distributed on an "AS IS" BASIS, | |
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | * See the License for the specific language governing permissions and | |
12 | * limitations under the License. | |
13 | */ | |
14 | ||
15 | #include <config.h> | |
16 | ||
17 | #include <getopt.h> | |
18 | #include <stdlib.h> | |
19 | #include <stdio.h> | |
20 | ||
21 | #include "command-line.h" | |
67d9b930 | 22 | #include "daemon.h" |
ac0630a2 RB |
23 | #include "dirs.h" |
24 | #include "fatal-signal.h" | |
4edcdcf4 RB |
25 | #include "hash.h" |
26 | #include "hmap.h" | |
ac0630a2 | 27 | #include "ovn/ovn-nb-idl.h" |
ec78987f | 28 | #include "ovn/ovn-sb-idl.h" |
ac0630a2 RB |
29 | #include "poll-loop.h" |
30 | #include "stream.h" | |
31 | #include "stream-ssl.h" | |
32 | #include "util.h" | |
4edcdcf4 | 33 | #include "uuid.h" |
ac0630a2 RB |
34 | #include "openvswitch/vlog.h" |
35 | ||
36 | VLOG_DEFINE_THIS_MODULE(ovn_nbd); | |
37 | ||
f93818dd RB |
38 | struct nbd_context { |
39 | struct ovsdb_idl *ovnnb_idl; | |
ec78987f | 40 | struct ovsdb_idl *ovnsb_idl; |
f93818dd | 41 | struct ovsdb_idl_txn *ovnnb_txn; |
3c78b3ca | 42 | struct ovsdb_idl_txn *ovnsb_txn; |
f93818dd RB |
43 | }; |
44 | ||
ac0630a2 | 45 | static const char *ovnnb_db; |
ec78987f | 46 | static const char *ovnsb_db; |
ac0630a2 RB |
47 | |
48 | static const char *default_db(void); | |
49 | ||
50 | static void | |
51 | usage(void) | |
52 | { | |
53 | printf("\ | |
54 | %s: OVN northbound management daemon\n\ | |
55 | usage: %s [OPTIONS]\n\ | |
56 | \n\ | |
57 | Options:\n\ | |
58 | --ovnnb-db=DATABASE connect to ovn-nb database at DATABASE\n\ | |
59 | (default: %s)\n\ | |
ec78987f | 60 | --ovnsb-db=DATABASE connect to ovn-sb database at DATABASE\n\ |
ac0630a2 RB |
61 | (default: %s)\n\ |
62 | -h, --help display this help message\n\ | |
63 | -o, --options list available options\n\ | |
64 | -V, --version display version information\n\ | |
65 | ", program_name, program_name, default_db(), default_db()); | |
67d9b930 | 66 | daemon_usage(); |
ac0630a2 RB |
67 | vlog_usage(); |
68 | stream_usage("database", true, true, false); | |
69 | } | |
70 | \f | |
4edcdcf4 RB |
71 | static int |
72 | compare_strings(const void *a_, const void *b_) | |
73 | { | |
74 | char *const *a = a_; | |
75 | char *const *b = b_; | |
76 | return strcmp(*a, *b); | |
77 | } | |
78 | ||
79 | /* | |
80 | * Determine whether 2 arrays of MAC addresses are the same. It's possible that | |
81 | * the lists could be *very* long and this check is being done a lot (every | |
82 | * time the OVN_Northbound database changes). | |
83 | */ | |
84 | static bool | |
85 | macs_equal(char **binding_macs_, size_t b_n_macs, | |
86 | char **lport_macs_, size_t l_n_macs) | |
87 | { | |
88 | char **binding_macs, **lport_macs; | |
89 | size_t bytes, i; | |
90 | ||
91 | if (b_n_macs != l_n_macs) { | |
92 | return false; | |
93 | } | |
94 | ||
95 | bytes = b_n_macs * sizeof binding_macs_[0]; | |
96 | binding_macs = xmalloc(bytes); | |
97 | lport_macs = xmalloc(bytes); | |
98 | ||
99 | memcpy(binding_macs, binding_macs_, bytes); | |
100 | memcpy(lport_macs, lport_macs_, bytes); | |
101 | ||
102 | qsort(binding_macs, b_n_macs, sizeof binding_macs[0], compare_strings); | |
103 | qsort(lport_macs, l_n_macs, sizeof lport_macs[0], compare_strings); | |
104 | ||
105 | for (i = 0; i < b_n_macs; i++) { | |
106 | if (strcmp(binding_macs[i], lport_macs[i])) { | |
107 | break; | |
108 | } | |
109 | } | |
110 | ||
111 | free(binding_macs); | |
112 | free(lport_macs); | |
113 | ||
114 | return (i == b_n_macs) ? true : false; | |
115 | } | |
116 | ||
117 | /* | |
118 | * When a change has occurred in the OVN_Northbound database, we go through and | |
ec78987f JP |
119 | * make sure that the contents of the Bindings table in the OVN_Southbound |
120 | * database are up to date with the logical ports defined in the | |
121 | * OVN_Northbound database. | |
4edcdcf4 | 122 | */ |
ac0630a2 | 123 | static void |
4edcdcf4 | 124 | set_bindings(struct nbd_context *ctx) |
ac0630a2 | 125 | { |
4edcdcf4 | 126 | struct hmap bindings_hmap; |
ec78987f | 127 | const struct sbrec_bindings *binding; |
4edcdcf4 RB |
128 | const struct nbrec_logical_port *lport; |
129 | ||
130 | struct binding_hash_node { | |
131 | struct hmap_node node; | |
ec78987f | 132 | const struct sbrec_bindings *binding; |
4edcdcf4 RB |
133 | } *hash_node, *hash_node_next; |
134 | ||
135 | /* | |
136 | * We will need to look up a binding for every logical port. We don't want | |
137 | * to have to do an O(n) search for every binding, so start out by hashing | |
138 | * them on the logical port. | |
139 | * | |
140 | * As we go through every logical port, we will update the binding if it | |
141 | * exists or create one otherwise. When the update is done, we'll remove it | |
142 | * from the hashmap. At the end, any bindings left in the hashmap are for | |
143 | * logical ports that have been deleted. | |
144 | */ | |
145 | hmap_init(&bindings_hmap); | |
146 | ||
ec78987f | 147 | SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) { |
cf1486e0 | 148 | hash_node = xzalloc(sizeof *hash_node); |
4edcdcf4 RB |
149 | hash_node->binding = binding; |
150 | hmap_insert(&bindings_hmap, &hash_node->node, | |
151 | hash_string(binding->logical_port, 0)); | |
152 | } | |
153 | ||
154 | NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) { | |
1d4e6b55 | 155 | binding = NULL; |
4edcdcf4 RB |
156 | HMAP_FOR_EACH_WITH_HASH(hash_node, node, |
157 | hash_string(lport->name, 0), &bindings_hmap) { | |
158 | if (!strcmp(lport->name, hash_node->binding->logical_port)) { | |
1d4e6b55 | 159 | binding = hash_node->binding; |
4edcdcf4 RB |
160 | break; |
161 | } | |
162 | } | |
163 | ||
1d4e6b55 | 164 | if (binding) { |
4edcdcf4 RB |
165 | /* We found an existing binding for this logical port. Update its |
166 | * contents. Right now the only thing we expect that could change | |
167 | * is the list of MAC addresses. */ | |
168 | ||
4edcdcf4 RB |
169 | hmap_remove(&bindings_hmap, &hash_node->node); |
170 | free(hash_node); | |
171 | hash_node = NULL; | |
172 | ||
173 | if (!macs_equal(binding->mac, binding->n_mac, | |
174 | lport->macs, lport->n_macs)) { | |
ec78987f | 175 | sbrec_bindings_set_mac(binding, |
4edcdcf4 RB |
176 | (const char **) lport->macs, lport->n_macs); |
177 | } | |
178 | } else { | |
179 | /* There is no binding for this logical port, so create one. */ | |
180 | ||
3c78b3ca | 181 | binding = sbrec_bindings_insert(ctx->ovnsb_txn); |
ec78987f JP |
182 | sbrec_bindings_set_logical_port(binding, lport->name); |
183 | sbrec_bindings_set_mac(binding, | |
4edcdcf4 RB |
184 | (const char **) lport->macs, lport->n_macs); |
185 | } | |
186 | } | |
187 | ||
188 | HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &bindings_hmap) { | |
189 | hmap_remove(&bindings_hmap, &hash_node->node); | |
ec78987f | 190 | sbrec_bindings_delete(hash_node->binding); |
4edcdcf4 RB |
191 | free(hash_node); |
192 | } | |
193 | hmap_destroy(&bindings_hmap); | |
194 | } | |
195 | ||
196 | static void | |
197 | ovnnb_db_changed(struct nbd_context *ctx) | |
198 | { | |
c29734fc | 199 | VLOG_DBG("ovn-nb db contents have changed."); |
4edcdcf4 RB |
200 | |
201 | set_bindings(ctx); | |
ac0630a2 RB |
202 | } |
203 | ||
f93818dd RB |
204 | /* |
205 | * The only change we get notified about is if the 'chassis' column of the | |
206 | * 'Bindings' table changes. When this column is not empty, it means we need to | |
207 | * set the corresponding logical port as 'up' in the northbound DB. | |
208 | */ | |
ac0630a2 | 209 | static void |
ec78987f | 210 | ovnsb_db_changed(struct nbd_context *ctx) |
ac0630a2 | 211 | { |
fc3113bc RB |
212 | struct hmap lports_hmap; |
213 | const struct sbrec_bindings *binding; | |
214 | const struct nbrec_logical_port *lport; | |
215 | ||
216 | struct lport_hash_node { | |
217 | struct hmap_node node; | |
218 | const struct nbrec_logical_port *lport; | |
219 | } *hash_node, *hash_node_next; | |
f93818dd RB |
220 | |
221 | VLOG_DBG("Recalculating port up states for ovn-nb db."); | |
222 | ||
fc3113bc | 223 | hmap_init(&lports_hmap); |
f93818dd | 224 | |
fc3113bc RB |
225 | NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) { |
226 | hash_node = xzalloc(sizeof *hash_node); | |
227 | hash_node->lport = lport; | |
228 | hmap_insert(&lports_hmap, &hash_node->node, | |
229 | hash_string(lport->name, 0)); | |
230 | } | |
231 | ||
232 | SBREC_BINDINGS_FOR_EACH(binding, ctx->ovnsb_idl) { | |
233 | lport = NULL; | |
234 | HMAP_FOR_EACH_WITH_HASH(hash_node, node, | |
235 | hash_string(binding->logical_port, 0), &lports_hmap) { | |
236 | if (!strcmp(binding->logical_port, hash_node->lport->name)) { | |
237 | lport = hash_node->lport; | |
238 | break; | |
239 | } | |
f93818dd RB |
240 | } |
241 | ||
f93818dd | 242 | if (!lport) { |
fc3113bc RB |
243 | /* The logical port doesn't exist for this binding. This can happen |
244 | * under normal circumstances when ovn-nbd hasn't gotten around to | |
245 | * pruning the Binding yet. */ | |
f93818dd RB |
246 | continue; |
247 | } | |
248 | ||
fc3113bc | 249 | if (*binding->chassis && (!lport->up || !*lport->up)) { |
f93818dd RB |
250 | bool up = true; |
251 | nbrec_logical_port_set_up(lport, &up, 1); | |
fc3113bc | 252 | } else if (!*binding->chassis && (!lport->up || *lport->up)) { |
f93818dd RB |
253 | bool up = false; |
254 | nbrec_logical_port_set_up(lport, &up, 1); | |
255 | } | |
256 | } | |
fc3113bc RB |
257 | |
258 | HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &lports_hmap) { | |
259 | hmap_remove(&lports_hmap, &hash_node->node); | |
260 | free(hash_node); | |
261 | } | |
262 | hmap_destroy(&lports_hmap); | |
ac0630a2 RB |
263 | } |
264 | \f | |
265 | static const char * | |
266 | default_db(void) | |
267 | { | |
268 | static char *def; | |
269 | if (!def) { | |
270 | def = xasprintf("unix:%s/db.sock", ovs_rundir()); | |
271 | } | |
272 | return def; | |
273 | } | |
274 | ||
275 | static void | |
276 | parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
277 | { | |
278 | enum { | |
67d9b930 | 279 | DAEMON_OPTION_ENUMS, |
ac0630a2 RB |
280 | VLOG_OPTION_ENUMS, |
281 | }; | |
282 | static const struct option long_options[] = { | |
ec78987f | 283 | {"ovnsb-db", required_argument, NULL, 'd'}, |
ac0630a2 RB |
284 | {"ovnnb-db", required_argument, NULL, 'D'}, |
285 | {"help", no_argument, NULL, 'h'}, | |
286 | {"options", no_argument, NULL, 'o'}, | |
287 | {"version", no_argument, NULL, 'V'}, | |
67d9b930 | 288 | DAEMON_LONG_OPTIONS, |
ac0630a2 RB |
289 | VLOG_LONG_OPTIONS, |
290 | STREAM_SSL_LONG_OPTIONS, | |
291 | {NULL, 0, NULL, 0}, | |
292 | }; | |
293 | char *short_options = ovs_cmdl_long_options_to_short_options(long_options); | |
294 | ||
295 | for (;;) { | |
296 | int c; | |
297 | ||
298 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
299 | if (c == -1) { | |
300 | break; | |
301 | } | |
302 | ||
303 | switch (c) { | |
67d9b930 | 304 | DAEMON_OPTION_HANDLERS; |
ac0630a2 RB |
305 | VLOG_OPTION_HANDLERS; |
306 | STREAM_SSL_OPTION_HANDLERS; | |
307 | ||
308 | case 'd': | |
ec78987f | 309 | ovnsb_db = optarg; |
ac0630a2 RB |
310 | break; |
311 | ||
312 | case 'D': | |
313 | ovnnb_db = optarg; | |
314 | break; | |
315 | ||
316 | case 'h': | |
317 | usage(); | |
318 | exit(EXIT_SUCCESS); | |
319 | ||
320 | case 'o': | |
321 | ovs_cmdl_print_options(long_options); | |
322 | exit(EXIT_SUCCESS); | |
323 | ||
324 | case 'V': | |
325 | ovs_print_version(0, 0); | |
326 | exit(EXIT_SUCCESS); | |
327 | ||
328 | default: | |
329 | break; | |
330 | } | |
331 | } | |
332 | ||
ec78987f JP |
333 | if (!ovnsb_db) { |
334 | ovnsb_db = default_db(); | |
ac0630a2 RB |
335 | } |
336 | ||
337 | if (!ovnnb_db) { | |
338 | ovnnb_db = default_db(); | |
339 | } | |
340 | ||
341 | free(short_options); | |
342 | } | |
343 | ||
344 | int | |
345 | main(int argc, char *argv[]) | |
346 | { | |
347 | extern struct vlog_module VLM_reconnect; | |
ec78987f | 348 | struct ovsdb_idl *ovnnb_idl, *ovnsb_idl; |
ac0630a2 RB |
349 | unsigned int ovnnb_seqno, ovn_seqno; |
350 | int res = EXIT_SUCCESS; | |
f93818dd | 351 | struct nbd_context ctx = { |
3c78b3ca | 352 | .ovnsb_txn = NULL, |
f93818dd RB |
353 | }; |
354 | bool ovnnb_changes_pending = false; | |
355 | bool ovn_changes_pending = false; | |
ac0630a2 RB |
356 | |
357 | fatal_ignore_sigpipe(); | |
358 | set_program_name(argv[0]); | |
359 | vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN); | |
360 | vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN); | |
361 | parse_options(argc, argv); | |
67d9b930 RB |
362 | |
363 | daemonize(); | |
364 | ||
ac0630a2 | 365 | nbrec_init(); |
ec78987f | 366 | sbrec_init(); |
ac0630a2 RB |
367 | |
368 | /* We want to detect all changes to the ovn-nb db. */ | |
f93818dd RB |
369 | ctx.ovnnb_idl = ovnnb_idl = ovsdb_idl_create(ovnnb_db, |
370 | &nbrec_idl_class, true, true); | |
ac0630a2 | 371 | |
91ae2065 | 372 | /* There is only a small subset of changes to the ovn-sb db that ovn-northd |
a0149f47 | 373 | * has to care about, so we'll enable monitoring those directly. */ |
ec78987f JP |
374 | ctx.ovnsb_idl = ovnsb_idl = ovsdb_idl_create(ovnsb_db, |
375 | &sbrec_idl_class, false, true); | |
376 | ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_bindings); | |
377 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_logical_port); | |
378 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_chassis); | |
379 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_bindings_col_mac); | |
ac0630a2 RB |
380 | |
381 | /* | |
382 | * The loop here just runs the IDL in a loop waiting for the seqno to | |
383 | * change, which indicates that the contents of the db have changed. | |
384 | * | |
a0149f47 JP |
385 | * If the contents of the ovn-nb db change, the mappings to the ovn-sb |
386 | * db must be recalculated. | |
ac0630a2 | 387 | * |
a0149f47 | 388 | * If the contents of the ovn-sb db change, it means the 'up' state of |
91ae2065 | 389 | * a port may have changed, as that's the only type of change ovn-northd is |
a0149f47 | 390 | * watching for. |
ac0630a2 RB |
391 | */ |
392 | ||
393 | ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl); | |
ec78987f | 394 | ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl); |
ac0630a2 RB |
395 | for (;;) { |
396 | ovsdb_idl_run(ovnnb_idl); | |
ec78987f | 397 | ovsdb_idl_run(ovnsb_idl); |
ac0630a2 RB |
398 | |
399 | if (!ovsdb_idl_is_alive(ovnnb_idl)) { | |
400 | int retval = ovsdb_idl_get_last_error(ovnnb_idl); | |
401 | VLOG_ERR("%s: database connection failed (%s)", | |
402 | ovnnb_db, ovs_retval_to_string(retval)); | |
403 | res = EXIT_FAILURE; | |
404 | break; | |
405 | } | |
406 | ||
ec78987f JP |
407 | if (!ovsdb_idl_is_alive(ovnsb_idl)) { |
408 | int retval = ovsdb_idl_get_last_error(ovnsb_idl); | |
ac0630a2 | 409 | VLOG_ERR("%s: database connection failed (%s)", |
ec78987f | 410 | ovnsb_db, ovs_retval_to_string(retval)); |
ac0630a2 RB |
411 | res = EXIT_FAILURE; |
412 | break; | |
413 | } | |
414 | ||
415 | if (ovnnb_seqno != ovsdb_idl_get_seqno(ovnnb_idl)) { | |
416 | ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl); | |
f93818dd | 417 | ovnnb_changes_pending = true; |
ac0630a2 RB |
418 | } |
419 | ||
ec78987f JP |
420 | if (ovn_seqno != ovsdb_idl_get_seqno(ovnsb_idl)) { |
421 | ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl); | |
f93818dd RB |
422 | ovn_changes_pending = true; |
423 | } | |
424 | ||
425 | /* | |
426 | * If there are any pending changes, we delay recalculating the | |
427 | * necessary updates until after an existing transaction finishes. | |
91ae2065 RB |
428 | * This avoids the possibility of rapid updates causing ovn-northd to |
429 | * never be able to successfully make the corresponding updates to the | |
430 | * other db. Instead, pending changes are batched up until the next | |
431 | * time we get a chance to calculate the new state and apply it. | |
f93818dd RB |
432 | */ |
433 | ||
3c78b3ca | 434 | if (ovnnb_changes_pending && !ctx.ovnsb_txn) { |
f93818dd RB |
435 | /* |
436 | * The OVN-nb db contents have changed, so create a transaction for | |
a0149f47 | 437 | * updating the OVN-sb DB. |
f93818dd | 438 | */ |
3c78b3ca | 439 | ctx.ovnsb_txn = ovsdb_idl_txn_create(ctx.ovnsb_idl); |
5da82071 | 440 | ovsdb_idl_txn_add_comment(ctx.ovnsb_txn, |
91ae2065 | 441 | "ovn-northd: northbound db changed"); |
f93818dd RB |
442 | ovnnb_db_changed(&ctx); |
443 | ovnnb_changes_pending = false; | |
444 | } | |
445 | ||
446 | if (ovn_changes_pending && !ctx.ovnnb_txn) { | |
447 | /* | |
a0149f47 | 448 | * The OVN-sb db contents have changed, so create a transaction for |
f93818dd RB |
449 | * updating the northbound DB. |
450 | */ | |
451 | ctx.ovnnb_txn = ovsdb_idl_txn_create(ctx.ovnnb_idl); | |
5da82071 | 452 | ovsdb_idl_txn_add_comment(ctx.ovnnb_txn, |
91ae2065 | 453 | "ovn-northd: southbound db changed"); |
ec78987f | 454 | ovnsb_db_changed(&ctx); |
f93818dd RB |
455 | ovn_changes_pending = false; |
456 | } | |
457 | ||
458 | if (ctx.ovnnb_txn) { | |
459 | enum ovsdb_idl_txn_status txn_status; | |
460 | txn_status = ovsdb_idl_txn_commit(ctx.ovnnb_txn); | |
461 | switch (txn_status) { | |
462 | case TXN_UNCOMMITTED: | |
463 | case TXN_INCOMPLETE: | |
464 | /* Come back around and try to commit this transaction again */ | |
465 | break; | |
466 | case TXN_ABORTED: | |
467 | case TXN_TRY_AGAIN: | |
468 | case TXN_NOT_LOCKED: | |
469 | case TXN_ERROR: | |
470 | /* Something went wrong, so try creating a new transaction. */ | |
471 | ovn_changes_pending = true; | |
472 | case TXN_UNCHANGED: | |
473 | case TXN_SUCCESS: | |
474 | ovsdb_idl_txn_destroy(ctx.ovnnb_txn); | |
475 | ctx.ovnnb_txn = NULL; | |
476 | } | |
477 | } | |
478 | ||
3c78b3ca | 479 | if (ctx.ovnsb_txn) { |
f93818dd | 480 | enum ovsdb_idl_txn_status txn_status; |
3c78b3ca | 481 | txn_status = ovsdb_idl_txn_commit(ctx.ovnsb_txn); |
f93818dd RB |
482 | switch (txn_status) { |
483 | case TXN_UNCOMMITTED: | |
484 | case TXN_INCOMPLETE: | |
485 | /* Come back around and try to commit this transaction again */ | |
486 | break; | |
487 | case TXN_ABORTED: | |
488 | case TXN_TRY_AGAIN: | |
489 | case TXN_NOT_LOCKED: | |
490 | case TXN_ERROR: | |
491 | /* Something went wrong, so try creating a new transaction. */ | |
492 | ovnnb_changes_pending = true; | |
493 | case TXN_UNCHANGED: | |
494 | case TXN_SUCCESS: | |
3c78b3ca JP |
495 | ovsdb_idl_txn_destroy(ctx.ovnsb_txn); |
496 | ctx.ovnsb_txn = NULL; | |
f93818dd | 497 | } |
ac0630a2 RB |
498 | } |
499 | ||
500 | if (ovnnb_seqno == ovsdb_idl_get_seqno(ovnnb_idl) && | |
ec78987f | 501 | ovn_seqno == ovsdb_idl_get_seqno(ovnsb_idl)) { |
ac0630a2 | 502 | ovsdb_idl_wait(ovnnb_idl); |
ec78987f | 503 | ovsdb_idl_wait(ovnsb_idl); |
f93818dd RB |
504 | if (ctx.ovnnb_txn) { |
505 | ovsdb_idl_txn_wait(ctx.ovnnb_txn); | |
506 | } | |
3c78b3ca JP |
507 | if (ctx.ovnsb_txn) { |
508 | ovsdb_idl_txn_wait(ctx.ovnsb_txn); | |
f93818dd | 509 | } |
ac0630a2 RB |
510 | poll_block(); |
511 | } | |
512 | } | |
513 | ||
ec78987f | 514 | ovsdb_idl_destroy(ovnsb_idl); |
ac0630a2 RB |
515 | ovsdb_idl_destroy(ovnnb_idl); |
516 | ||
517 | exit(res); | |
518 | } |