]>
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 | 23 | #include "dirs.h" |
bd39395f | 24 | #include "dynamic-string.h" |
ac0630a2 | 25 | #include "fatal-signal.h" |
4edcdcf4 RB |
26 | #include "hash.h" |
27 | #include "hmap.h" | |
bd39395f BP |
28 | #include "json.h" |
29 | #include "ovn/lib/lex.h" | |
e3df8838 BP |
30 | #include "ovn/lib/ovn-nb-idl.h" |
31 | #include "ovn/lib/ovn-sb-idl.h" | |
ac0630a2 RB |
32 | #include "poll-loop.h" |
33 | #include "stream.h" | |
34 | #include "stream-ssl.h" | |
35 | #include "util.h" | |
4edcdcf4 | 36 | #include "uuid.h" |
ac0630a2 RB |
37 | #include "openvswitch/vlog.h" |
38 | ||
2e2762d4 | 39 | VLOG_DEFINE_THIS_MODULE(ovn_northd); |
ac0630a2 | 40 | |
2e2762d4 | 41 | struct northd_context { |
f93818dd | 42 | struct ovsdb_idl *ovnnb_idl; |
ec78987f | 43 | struct ovsdb_idl *ovnsb_idl; |
f93818dd | 44 | struct ovsdb_idl_txn *ovnnb_txn; |
3c78b3ca | 45 | struct ovsdb_idl_txn *ovnsb_txn; |
f93818dd RB |
46 | }; |
47 | ||
ac0630a2 | 48 | static const char *ovnnb_db; |
ec78987f | 49 | static const char *ovnsb_db; |
ac0630a2 RB |
50 | |
51 | static const char *default_db(void); | |
52 | ||
53 | static void | |
54 | usage(void) | |
55 | { | |
56 | printf("\ | |
57 | %s: OVN northbound management daemon\n\ | |
58 | usage: %s [OPTIONS]\n\ | |
59 | \n\ | |
60 | Options:\n\ | |
61 | --ovnnb-db=DATABASE connect to ovn-nb database at DATABASE\n\ | |
62 | (default: %s)\n\ | |
ec78987f | 63 | --ovnsb-db=DATABASE connect to ovn-sb database at DATABASE\n\ |
ac0630a2 RB |
64 | (default: %s)\n\ |
65 | -h, --help display this help message\n\ | |
66 | -o, --options list available options\n\ | |
67 | -V, --version display version information\n\ | |
68 | ", program_name, program_name, default_db(), default_db()); | |
67d9b930 | 69 | daemon_usage(); |
ac0630a2 RB |
70 | vlog_usage(); |
71 | stream_usage("database", true, true, false); | |
72 | } | |
73 | \f | |
4edcdcf4 RB |
74 | static int |
75 | compare_strings(const void *a_, const void *b_) | |
76 | { | |
77 | char *const *a = a_; | |
78 | char *const *b = b_; | |
79 | return strcmp(*a, *b); | |
80 | } | |
81 | ||
82 | /* | |
83 | * Determine whether 2 arrays of MAC addresses are the same. It's possible that | |
84 | * the lists could be *very* long and this check is being done a lot (every | |
85 | * time the OVN_Northbound database changes). | |
86 | */ | |
87 | static bool | |
88 | macs_equal(char **binding_macs_, size_t b_n_macs, | |
89 | char **lport_macs_, size_t l_n_macs) | |
90 | { | |
91 | char **binding_macs, **lport_macs; | |
92 | size_t bytes, i; | |
93 | ||
94 | if (b_n_macs != l_n_macs) { | |
95 | return false; | |
96 | } | |
97 | ||
98 | bytes = b_n_macs * sizeof binding_macs_[0]; | |
99 | binding_macs = xmalloc(bytes); | |
100 | lport_macs = xmalloc(bytes); | |
101 | ||
102 | memcpy(binding_macs, binding_macs_, bytes); | |
103 | memcpy(lport_macs, lport_macs_, bytes); | |
104 | ||
105 | qsort(binding_macs, b_n_macs, sizeof binding_macs[0], compare_strings); | |
106 | qsort(lport_macs, l_n_macs, sizeof lport_macs[0], compare_strings); | |
107 | ||
108 | for (i = 0; i < b_n_macs; i++) { | |
109 | if (strcmp(binding_macs[i], lport_macs[i])) { | |
110 | break; | |
111 | } | |
112 | } | |
113 | ||
114 | free(binding_macs); | |
115 | free(lport_macs); | |
116 | ||
117 | return (i == b_n_macs) ? true : false; | |
118 | } | |
bd39395f BP |
119 | \f |
120 | /* Pipeline generation. | |
121 | * | |
122 | * This code generates the Pipeline table in the southbound database, as a | |
123 | * function of most of the northbound database. | |
124 | */ | |
125 | ||
126 | /* Enough context to add a Pipeline row, using pipeline_add(). */ | |
127 | struct pipeline_ctx { | |
128 | /* From northd_context. */ | |
129 | struct ovsdb_idl *ovnsb_idl; | |
130 | struct ovsdb_idl_txn *ovnsb_txn; | |
131 | ||
132 | /* Contains "struct pipeline_hash_node"s. Used to figure out what existing | |
133 | * Pipeline rows should be deleted: we index all of the Pipeline rows into | |
134 | * this data structure, then as existing rows are generated we remove them. | |
135 | * After generating all the rows, any remaining in 'pipeline_hmap' must be | |
136 | * deleted from the database. */ | |
137 | struct hmap pipeline_hmap; | |
138 | }; | |
139 | ||
140 | /* A row in the Pipeline table, indexed by its full contents, */ | |
141 | struct pipeline_hash_node { | |
142 | struct hmap_node node; | |
143 | const struct sbrec_pipeline *pipeline; | |
144 | }; | |
145 | ||
146 | static size_t | |
147 | pipeline_hash(const struct uuid *logical_datapath, uint8_t table_id, | |
148 | uint16_t priority, const char *match, const char *actions) | |
149 | { | |
150 | size_t hash = uuid_hash(logical_datapath); | |
151 | hash = hash_2words((table_id << 16) | priority, hash); | |
152 | hash = hash_string(match, hash); | |
153 | return hash_string(actions, hash); | |
154 | } | |
155 | ||
156 | static size_t | |
157 | pipeline_hash_rec(const struct sbrec_pipeline *pipeline) | |
158 | { | |
159 | return pipeline_hash(&pipeline->logical_datapath, pipeline->table_id, | |
160 | pipeline->priority, pipeline->match, | |
161 | pipeline->actions); | |
162 | } | |
163 | ||
164 | /* Adds a row with the specified contents to the Pipeline table. */ | |
165 | static void | |
166 | pipeline_add(struct pipeline_ctx *ctx, | |
167 | const struct nbrec_logical_switch *logical_datapath, | |
168 | uint8_t table_id, | |
169 | uint16_t priority, | |
170 | const char *match, | |
171 | const char *actions) | |
172 | { | |
173 | struct pipeline_hash_node *hash_node; | |
174 | ||
175 | /* Check whether such a row already exists in the Pipeline table. If so, | |
176 | * remove it from 'ctx->pipeline_hmap' and we're done. */ | |
177 | HMAP_FOR_EACH_WITH_HASH (hash_node, node, | |
178 | pipeline_hash(&logical_datapath->header_.uuid, | |
179 | table_id, priority, match, actions), | |
180 | &ctx->pipeline_hmap) { | |
181 | const struct sbrec_pipeline *pipeline = hash_node->pipeline; | |
182 | if (uuid_equals(&pipeline->logical_datapath, | |
183 | &logical_datapath->header_.uuid) | |
184 | && pipeline->table_id == table_id | |
185 | && pipeline->priority == priority | |
186 | && !strcmp(pipeline->match, match) | |
187 | && !strcmp(pipeline->actions, actions)) { | |
188 | hmap_remove(&ctx->pipeline_hmap, &hash_node->node); | |
189 | free(hash_node); | |
190 | return; | |
191 | } | |
192 | } | |
193 | ||
194 | /* No such Pipeline row. Add one. */ | |
195 | const struct sbrec_pipeline *pipeline; | |
196 | pipeline = sbrec_pipeline_insert(ctx->ovnsb_txn); | |
197 | sbrec_pipeline_set_logical_datapath(pipeline, | |
198 | logical_datapath->header_.uuid); | |
199 | sbrec_pipeline_set_table_id(pipeline, table_id); | |
200 | sbrec_pipeline_set_priority(pipeline, priority); | |
201 | sbrec_pipeline_set_match(pipeline, match); | |
202 | sbrec_pipeline_set_actions(pipeline, actions); | |
203 | } | |
204 | ||
bd39395f BP |
205 | /* Appends port security constraints on L2 address field 'eth_addr_field' |
206 | * (e.g. "eth.src" or "eth.dst") to 'match'. 'port_security', with | |
207 | * 'n_port_security' elements, is the collection of port_security constraints | |
f7cb14cd | 208 | * from an OVN_NB Logical_Port row. */ |
bd39395f BP |
209 | static void |
210 | build_port_security(const char *eth_addr_field, | |
211 | char **port_security, size_t n_port_security, | |
212 | struct ds *match) | |
213 | { | |
214 | size_t base_len = match->length; | |
215 | ds_put_format(match, " && %s == {", eth_addr_field); | |
216 | ||
217 | size_t n = 0; | |
218 | for (size_t i = 0; i < n_port_security; i++) { | |
f7cb14cd BP |
219 | uint8_t ea[ETH_ADDR_LEN]; |
220 | ||
221 | if (eth_addr_from_string(port_security[i], ea)) { | |
222 | ds_put_format(match, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea)); | |
bd39395f BP |
223 | ds_put_char(match, ' '); |
224 | n++; | |
225 | } | |
226 | } | |
f7cb14cd | 227 | ds_chomp(match, ' '); |
bd39395f | 228 | ds_put_cstr(match, "}"); |
4edcdcf4 | 229 | |
bd39395f BP |
230 | if (!n) { |
231 | match->length = base_len; | |
232 | } | |
233 | } | |
234 | ||
235 | /* Updates the Pipeline table in the OVN_SB database, constructing its contents | |
236 | * based on the OVN_NB database. */ | |
237 | static void | |
238 | build_pipeline(struct northd_context *ctx) | |
239 | { | |
240 | struct pipeline_ctx pc = { | |
241 | .ovnsb_idl = ctx->ovnsb_idl, | |
242 | .ovnsb_txn = ctx->ovnsb_txn, | |
243 | .pipeline_hmap = HMAP_INITIALIZER(&pc.pipeline_hmap) | |
244 | }; | |
245 | ||
246 | /* Add all the Pipeline entries currently in the southbound database to | |
247 | * 'pc.pipeline_hmap'. We remove entries that we generate from the hmap, | |
248 | * thus by the time we're done only entries that need to be removed | |
249 | * remain. */ | |
250 | const struct sbrec_pipeline *pipeline; | |
251 | SBREC_PIPELINE_FOR_EACH (pipeline, ctx->ovnsb_idl) { | |
252 | struct pipeline_hash_node *hash_node = xzalloc(sizeof *hash_node); | |
253 | hash_node->pipeline = pipeline; | |
254 | hmap_insert(&pc.pipeline_hmap, &hash_node->node, | |
255 | pipeline_hash_rec(pipeline)); | |
256 | } | |
257 | ||
258 | /* Table 0: Admission control framework. */ | |
259 | const struct nbrec_logical_switch *lswitch; | |
260 | NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) { | |
261 | /* Logical VLANs not supported. */ | |
5e3b3449 | 262 | pipeline_add(&pc, lswitch, 0, 100, "vlan.present", "drop;"); |
bd39395f BP |
263 | |
264 | /* Broadcast/multicast source address is invalid. */ | |
5e3b3449 | 265 | pipeline_add(&pc, lswitch, 0, 100, "eth.src[40]", "drop;"); |
bd39395f | 266 | |
35060cdc BP |
267 | /* Port security flows have priority 50 (see below) and will continue |
268 | * to the next table if packet source is acceptable. */ | |
bd39395f BP |
269 | |
270 | /* Otherwise drop the packet. */ | |
5e3b3449 | 271 | pipeline_add(&pc, lswitch, 0, 0, "1", "drop;"); |
bd39395f BP |
272 | } |
273 | ||
274 | /* Table 0: Ingress port security. */ | |
275 | const struct nbrec_logical_port *lport; | |
276 | NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) { | |
277 | struct ds match = DS_EMPTY_INITIALIZER; | |
278 | ds_put_cstr(&match, "inport == "); | |
279 | json_string_escape(lport->name, &match); | |
280 | build_port_security("eth.src", | |
281 | lport->port_security, lport->n_port_security, | |
282 | &match); | |
35060cdc | 283 | pipeline_add(&pc, lport->lswitch, 0, 50, ds_cstr(&match), "next;"); |
bd39395f BP |
284 | ds_destroy(&match); |
285 | } | |
286 | ||
287 | /* Table 1: Destination lookup, broadcast and multicast handling (priority | |
288 | * 100). */ | |
289 | NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) { | |
290 | struct ds actions; | |
291 | ||
292 | ds_init(&actions); | |
293 | NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) { | |
294 | if (lport->lswitch == lswitch) { | |
295 | ds_put_cstr(&actions, "outport = "); | |
296 | json_string_escape(lport->name, &actions); | |
35060cdc | 297 | ds_put_cstr(&actions, "; next; "); |
bd39395f BP |
298 | } |
299 | } | |
300 | ds_chomp(&actions, ' '); | |
301 | ||
302 | pipeline_add(&pc, lswitch, 1, 100, "eth.dst[40]", ds_cstr(&actions)); | |
303 | ds_destroy(&actions); | |
304 | } | |
305 | ||
306 | /* Table 1: Destination lookup, unicast handling (priority 50), */ | |
2fd81197 BP |
307 | struct unknown_actions { |
308 | struct hmap_node hmap_node; | |
309 | const struct nbrec_logical_switch *ls; | |
310 | struct ds actions; | |
311 | }; | |
312 | struct hmap unknown_actions = HMAP_INITIALIZER(&unknown_actions); | |
bd39395f | 313 | NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) { |
2fd81197 | 314 | lswitch = lport->lswitch; |
bd39395f BP |
315 | for (size_t i = 0; i < lport->n_macs; i++) { |
316 | uint8_t mac[ETH_ADDR_LEN]; | |
317 | ||
318 | if (eth_addr_from_string(lport->macs[i], mac)) { | |
319 | struct ds match, actions; | |
320 | ||
321 | ds_init(&match); | |
322 | ds_put_format(&match, "eth.dst == %s", lport->macs[i]); | |
323 | ||
324 | ds_init(&actions); | |
325 | ds_put_cstr(&actions, "outport = "); | |
326 | json_string_escape(lport->name, &actions); | |
35060cdc | 327 | ds_put_cstr(&actions, "; next;"); |
2fd81197 | 328 | pipeline_add(&pc, lswitch, 1, 50, |
bd39395f BP |
329 | ds_cstr(&match), ds_cstr(&actions)); |
330 | ds_destroy(&actions); | |
331 | ds_destroy(&match); | |
332 | } else if (!strcmp(lport->macs[i], "unknown")) { | |
2fd81197 BP |
333 | const struct uuid *uuid = &lswitch->header_.uuid; |
334 | struct unknown_actions *ua = NULL; | |
335 | struct unknown_actions *iter; | |
336 | HMAP_FOR_EACH_WITH_HASH (iter, hmap_node, uuid_hash(uuid), | |
337 | &unknown_actions) { | |
338 | if (uuid_equals(&iter->ls->header_.uuid, uuid)) { | |
339 | ua = iter; | |
340 | break; | |
341 | } | |
342 | } | |
343 | if (!ua) { | |
344 | ua = xmalloc(sizeof *ua); | |
345 | hmap_insert(&unknown_actions, &ua->hmap_node, | |
346 | uuid_hash(uuid)); | |
347 | ua->ls = lswitch; | |
348 | ds_init(&ua->actions); | |
349 | } else { | |
350 | ds_put_char(&ua->actions, ' '); | |
351 | } | |
352 | ||
353 | ds_put_cstr(&ua->actions, "outport = "); | |
354 | json_string_escape(lport->name, &ua->actions); | |
35060cdc | 355 | ds_put_cstr(&ua->actions, "; next;"); |
bd39395f BP |
356 | } else { |
357 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); | |
358 | ||
359 | VLOG_INFO_RL(&rl, "%s: invalid syntax '%s' in macs column", | |
360 | lport->name, lport->macs[i]); | |
361 | } | |
362 | } | |
363 | } | |
364 | ||
365 | /* Table 1: Destination lookup for unknown MACs (priority 0). */ | |
2fd81197 BP |
366 | struct unknown_actions *ua, *next_ua; |
367 | HMAP_FOR_EACH_SAFE (ua, next_ua, hmap_node, &unknown_actions) { | |
368 | pipeline_add(&pc, ua->ls, 1, 0, "1", ds_cstr(&ua->actions)); | |
369 | hmap_remove(&unknown_actions, &ua->hmap_node); | |
370 | ds_destroy(&ua->actions); | |
371 | free(ua); | |
bd39395f | 372 | } |
2fd81197 | 373 | hmap_destroy(&unknown_actions); |
bd39395f BP |
374 | |
375 | /* Table 2: ACLs. */ | |
376 | const struct nbrec_acl *acl; | |
377 | NBREC_ACL_FOR_EACH (acl, ctx->ovnnb_idl) { | |
378 | const char *action; | |
379 | ||
380 | action = (!strcmp(acl->action, "allow") || | |
5e3b3449 | 381 | !strcmp(acl->action, "allow-related")) |
35060cdc | 382 | ? "next;" : "drop;"; |
bd39395f BP |
383 | pipeline_add(&pc, acl->lswitch, 2, acl->priority, acl->match, action); |
384 | } | |
385 | NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) { | |
35060cdc | 386 | pipeline_add(&pc, lswitch, 2, 0, "1", "next;"); |
bd39395f BP |
387 | } |
388 | ||
389 | /* Table 3: Egress port security. */ | |
2f60b7b7 BP |
390 | NBREC_LOGICAL_SWITCH_FOR_EACH (lswitch, ctx->ovnnb_idl) { |
391 | pipeline_add(&pc, lswitch, 3, 100, "eth.dst[40]", "output;"); | |
392 | } | |
bd39395f | 393 | NBREC_LOGICAL_PORT_FOR_EACH (lport, ctx->ovnnb_idl) { |
35060cdc | 394 | struct ds match; |
bd39395f BP |
395 | |
396 | ds_init(&match); | |
397 | ds_put_cstr(&match, "outport == "); | |
398 | json_string_escape(lport->name, &match); | |
399 | build_port_security("eth.dst", | |
400 | lport->port_security, lport->n_port_security, | |
401 | &match); | |
402 | ||
35060cdc | 403 | pipeline_add(&pc, lport->lswitch, 3, 50, ds_cstr(&match), "output;"); |
bd39395f | 404 | |
bd39395f BP |
405 | ds_destroy(&match); |
406 | } | |
407 | ||
408 | /* Delete any existing Pipeline rows that were not re-generated. */ | |
409 | struct pipeline_hash_node *hash_node, *next_hash_node; | |
410 | HMAP_FOR_EACH_SAFE (hash_node, next_hash_node, node, &pc.pipeline_hmap) { | |
411 | hmap_remove(&pc.pipeline_hmap, &hash_node->node); | |
412 | sbrec_pipeline_delete(hash_node->pipeline); | |
413 | free(hash_node); | |
414 | } | |
415 | hmap_destroy(&pc.pipeline_hmap); | |
416 | } | |
417 | \f | |
48f42f3a | 418 | static bool |
e387e3e8 | 419 | parents_equal(const struct sbrec_binding *binding, |
48f42f3a RB |
420 | const struct nbrec_logical_port *lport) |
421 | { | |
422 | if (!!binding->parent_port != !!lport->parent_name) { | |
423 | /* One is set and the other is not. */ | |
424 | return false; | |
425 | } | |
426 | ||
427 | if (binding->parent_port) { | |
428 | /* Both are set. */ | |
429 | return strcmp(binding->parent_port, lport->parent_name) ? false : true; | |
430 | } | |
431 | ||
432 | /* Both are NULL. */ | |
433 | return true; | |
434 | } | |
435 | ||
436 | static bool | |
e387e3e8 | 437 | tags_equal(const struct sbrec_binding *binding, |
48f42f3a RB |
438 | const struct nbrec_logical_port *lport) |
439 | { | |
440 | if (binding->n_tag != lport->n_tag) { | |
441 | return false; | |
442 | } | |
443 | ||
444 | return binding->n_tag ? (binding->tag[0] == lport->tag[0]) : true; | |
445 | } | |
446 | ||
eb00399e BP |
447 | struct binding_hash_node { |
448 | struct hmap_node lp_node; /* In 'lp_map', by binding->logical_port. */ | |
449 | struct hmap_node tk_node; /* In 'tk_map', by binding->tunnel_key. */ | |
e387e3e8 | 450 | const struct sbrec_binding *binding; |
eb00399e BP |
451 | }; |
452 | ||
453 | static bool | |
454 | tunnel_key_in_use(const struct hmap *tk_hmap, uint16_t tunnel_key) | |
455 | { | |
456 | const struct binding_hash_node *hash_node; | |
457 | ||
458 | HMAP_FOR_EACH_IN_BUCKET (hash_node, tk_node, hash_int(tunnel_key, 0), | |
459 | tk_hmap) { | |
460 | if (hash_node->binding->tunnel_key == tunnel_key) { | |
461 | return true; | |
462 | } | |
463 | } | |
464 | return false; | |
465 | } | |
466 | ||
467 | /* Chooses and returns a positive tunnel key that is not already in use in | |
468 | * 'tk_hmap'. Returns 0 if all tunnel keys are in use. */ | |
469 | static uint16_t | |
470 | choose_tunnel_key(const struct hmap *tk_hmap) | |
471 | { | |
472 | static uint16_t prev; | |
473 | ||
474 | for (uint16_t key = prev + 1; key != prev; key++) { | |
475 | if (!tunnel_key_in_use(tk_hmap, key)) { | |
476 | prev = key; | |
477 | return key; | |
478 | } | |
479 | } | |
480 | ||
481 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); | |
482 | VLOG_WARN_RL(&rl, "all tunnel keys exhausted"); | |
483 | return 0; | |
484 | } | |
485 | ||
4edcdcf4 RB |
486 | /* |
487 | * When a change has occurred in the OVN_Northbound database, we go through and | |
e387e3e8 | 488 | * make sure that the contents of the Binding table in the OVN_Southbound |
ec78987f JP |
489 | * database are up to date with the logical ports defined in the |
490 | * OVN_Northbound database. | |
4edcdcf4 | 491 | */ |
ac0630a2 | 492 | static void |
2e2762d4 | 493 | set_bindings(struct northd_context *ctx) |
ac0630a2 | 494 | { |
e387e3e8 | 495 | const struct sbrec_binding *binding; |
4edcdcf4 RB |
496 | const struct nbrec_logical_port *lport; |
497 | ||
4edcdcf4 RB |
498 | /* |
499 | * We will need to look up a binding for every logical port. We don't want | |
500 | * to have to do an O(n) search for every binding, so start out by hashing | |
501 | * them on the logical port. | |
502 | * | |
503 | * As we go through every logical port, we will update the binding if it | |
eb00399e BP |
504 | * exists or create one otherwise. When the update is done, we'll remove |
505 | * it from the hashmap. At the end, any bindings left in the hashmap are | |
506 | * for logical ports that have been deleted. | |
507 | * | |
508 | * We index the logical_port column because that's the shared key between | |
509 | * the OVN_NB and OVN_SB databases. We index the tunnel_key column to | |
510 | * allow us to choose a unique tunnel key for any Binding rows we have to | |
511 | * add. | |
4edcdcf4 | 512 | */ |
eb00399e BP |
513 | struct hmap lp_hmap = HMAP_INITIALIZER(&lp_hmap); |
514 | struct hmap tk_hmap = HMAP_INITIALIZER(&tk_hmap); | |
4edcdcf4 | 515 | |
e387e3e8 | 516 | SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) { |
eb00399e | 517 | struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node); |
4edcdcf4 | 518 | hash_node->binding = binding; |
eb00399e BP |
519 | hmap_insert(&lp_hmap, &hash_node->lp_node, |
520 | hash_string(binding->logical_port, 0)); | |
521 | hmap_insert(&tk_hmap, &hash_node->tk_node, | |
522 | hash_int(binding->tunnel_key, 0)); | |
4edcdcf4 RB |
523 | } |
524 | ||
525 | NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) { | |
eb00399e | 526 | struct binding_hash_node *hash_node; |
1d4e6b55 | 527 | binding = NULL; |
eb00399e BP |
528 | HMAP_FOR_EACH_WITH_HASH(hash_node, lp_node, |
529 | hash_string(lport->name, 0), &lp_hmap) { | |
4edcdcf4 | 530 | if (!strcmp(lport->name, hash_node->binding->logical_port)) { |
1d4e6b55 | 531 | binding = hash_node->binding; |
4edcdcf4 RB |
532 | break; |
533 | } | |
534 | } | |
535 | ||
03f455bd BP |
536 | struct uuid logical_datapath; |
537 | if (lport->lswitch) { | |
538 | logical_datapath = lport->lswitch->header_.uuid; | |
539 | } else { | |
540 | uuid_zero(&logical_datapath); | |
541 | } | |
542 | ||
1d4e6b55 | 543 | if (binding) { |
4edcdcf4 | 544 | /* We found an existing binding for this logical port. Update its |
48f42f3a | 545 | * contents. */ |
4edcdcf4 | 546 | |
eb00399e | 547 | hmap_remove(&lp_hmap, &hash_node->lp_node); |
4edcdcf4 RB |
548 | |
549 | if (!macs_equal(binding->mac, binding->n_mac, | |
550 | lport->macs, lport->n_macs)) { | |
e387e3e8 | 551 | sbrec_binding_set_mac(binding, |
4edcdcf4 RB |
552 | (const char **) lport->macs, lport->n_macs); |
553 | } | |
48f42f3a | 554 | if (!parents_equal(binding, lport)) { |
e387e3e8 | 555 | sbrec_binding_set_parent_port(binding, lport->parent_name); |
48f42f3a RB |
556 | } |
557 | if (!tags_equal(binding, lport)) { | |
e387e3e8 | 558 | sbrec_binding_set_tag(binding, lport->tag, lport->n_tag); |
48f42f3a | 559 | } |
03f455bd | 560 | if (!uuid_equals(&binding->logical_datapath, &logical_datapath)) { |
e387e3e8 | 561 | sbrec_binding_set_logical_datapath(binding, |
03f455bd BP |
562 | logical_datapath); |
563 | } | |
4edcdcf4 RB |
564 | } else { |
565 | /* There is no binding for this logical port, so create one. */ | |
566 | ||
eb00399e BP |
567 | uint16_t tunnel_key = choose_tunnel_key(&tk_hmap); |
568 | if (!tunnel_key) { | |
569 | continue; | |
570 | } | |
571 | ||
e387e3e8 BP |
572 | binding = sbrec_binding_insert(ctx->ovnsb_txn); |
573 | sbrec_binding_set_logical_port(binding, lport->name); | |
574 | sbrec_binding_set_mac(binding, | |
4edcdcf4 | 575 | (const char **) lport->macs, lport->n_macs); |
48f42f3a | 576 | if (lport->parent_name && lport->n_tag > 0) { |
e387e3e8 BP |
577 | sbrec_binding_set_parent_port(binding, lport->parent_name); |
578 | sbrec_binding_set_tag(binding, lport->tag, lport->n_tag); | |
48f42f3a | 579 | } |
03f455bd | 580 | |
e387e3e8 BP |
581 | sbrec_binding_set_tunnel_key(binding, tunnel_key); |
582 | sbrec_binding_set_logical_datapath(binding, logical_datapath); | |
eb00399e BP |
583 | |
584 | /* Add the tunnel key to the tk_hmap so that we don't try to use it | |
585 | * for another port. (We don't want it in the lp_hmap because that | |
e387e3e8 | 586 | * would just get the Binding record deleted later.) */ |
eb00399e BP |
587 | struct binding_hash_node *hash_node = xzalloc(sizeof *hash_node); |
588 | hash_node->binding = binding; | |
589 | hmap_insert(&tk_hmap, &hash_node->tk_node, | |
590 | hash_int(binding->tunnel_key, 0)); | |
4edcdcf4 RB |
591 | } |
592 | } | |
593 | ||
eb00399e BP |
594 | struct binding_hash_node *hash_node; |
595 | HMAP_FOR_EACH (hash_node, lp_node, &lp_hmap) { | |
596 | hmap_remove(&lp_hmap, &hash_node->lp_node); | |
e387e3e8 | 597 | sbrec_binding_delete(hash_node->binding); |
eb00399e BP |
598 | } |
599 | hmap_destroy(&lp_hmap); | |
600 | ||
601 | struct binding_hash_node *hash_node_next; | |
602 | HMAP_FOR_EACH_SAFE (hash_node, hash_node_next, tk_node, &tk_hmap) { | |
603 | hmap_remove(&tk_hmap, &hash_node->tk_node); | |
4edcdcf4 RB |
604 | free(hash_node); |
605 | } | |
eb00399e | 606 | hmap_destroy(&tk_hmap); |
4edcdcf4 RB |
607 | } |
608 | ||
609 | static void | |
2e2762d4 | 610 | ovnnb_db_changed(struct northd_context *ctx) |
4edcdcf4 | 611 | { |
c29734fc | 612 | VLOG_DBG("ovn-nb db contents have changed."); |
4edcdcf4 RB |
613 | |
614 | set_bindings(ctx); | |
bd39395f | 615 | build_pipeline(ctx); |
ac0630a2 RB |
616 | } |
617 | ||
f93818dd RB |
618 | /* |
619 | * The only change we get notified about is if the 'chassis' column of the | |
e387e3e8 | 620 | * 'Binding' table changes. When this column is not empty, it means we need to |
f93818dd RB |
621 | * set the corresponding logical port as 'up' in the northbound DB. |
622 | */ | |
ac0630a2 | 623 | static void |
2e2762d4 | 624 | ovnsb_db_changed(struct northd_context *ctx) |
ac0630a2 | 625 | { |
fc3113bc | 626 | struct hmap lports_hmap; |
e387e3e8 | 627 | const struct sbrec_binding *binding; |
fc3113bc RB |
628 | const struct nbrec_logical_port *lport; |
629 | ||
630 | struct lport_hash_node { | |
631 | struct hmap_node node; | |
632 | const struct nbrec_logical_port *lport; | |
633 | } *hash_node, *hash_node_next; | |
f93818dd RB |
634 | |
635 | VLOG_DBG("Recalculating port up states for ovn-nb db."); | |
636 | ||
fc3113bc | 637 | hmap_init(&lports_hmap); |
f93818dd | 638 | |
fc3113bc RB |
639 | NBREC_LOGICAL_PORT_FOR_EACH(lport, ctx->ovnnb_idl) { |
640 | hash_node = xzalloc(sizeof *hash_node); | |
641 | hash_node->lport = lport; | |
642 | hmap_insert(&lports_hmap, &hash_node->node, | |
643 | hash_string(lport->name, 0)); | |
644 | } | |
645 | ||
e387e3e8 | 646 | SBREC_BINDING_FOR_EACH(binding, ctx->ovnsb_idl) { |
fc3113bc RB |
647 | lport = NULL; |
648 | HMAP_FOR_EACH_WITH_HASH(hash_node, node, | |
649 | hash_string(binding->logical_port, 0), &lports_hmap) { | |
650 | if (!strcmp(binding->logical_port, hash_node->lport->name)) { | |
651 | lport = hash_node->lport; | |
652 | break; | |
653 | } | |
f93818dd RB |
654 | } |
655 | ||
f93818dd | 656 | if (!lport) { |
2e2762d4 BP |
657 | /* The logical port doesn't exist for this binding. This can |
658 | * happen under normal circumstances when ovn-northd hasn't gotten | |
659 | * around to pruning the Binding yet. */ | |
f93818dd RB |
660 | continue; |
661 | } | |
662 | ||
fc3113bc | 663 | if (*binding->chassis && (!lport->up || !*lport->up)) { |
f93818dd RB |
664 | bool up = true; |
665 | nbrec_logical_port_set_up(lport, &up, 1); | |
fc3113bc | 666 | } else if (!*binding->chassis && (!lport->up || *lport->up)) { |
f93818dd RB |
667 | bool up = false; |
668 | nbrec_logical_port_set_up(lport, &up, 1); | |
669 | } | |
670 | } | |
fc3113bc RB |
671 | |
672 | HMAP_FOR_EACH_SAFE(hash_node, hash_node_next, node, &lports_hmap) { | |
673 | hmap_remove(&lports_hmap, &hash_node->node); | |
674 | free(hash_node); | |
675 | } | |
676 | hmap_destroy(&lports_hmap); | |
ac0630a2 RB |
677 | } |
678 | \f | |
679 | static const char * | |
680 | default_db(void) | |
681 | { | |
682 | static char *def; | |
683 | if (!def) { | |
684 | def = xasprintf("unix:%s/db.sock", ovs_rundir()); | |
685 | } | |
686 | return def; | |
687 | } | |
688 | ||
689 | static void | |
690 | parse_options(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
691 | { | |
692 | enum { | |
67d9b930 | 693 | DAEMON_OPTION_ENUMS, |
ac0630a2 RB |
694 | VLOG_OPTION_ENUMS, |
695 | }; | |
696 | static const struct option long_options[] = { | |
ec78987f | 697 | {"ovnsb-db", required_argument, NULL, 'd'}, |
ac0630a2 RB |
698 | {"ovnnb-db", required_argument, NULL, 'D'}, |
699 | {"help", no_argument, NULL, 'h'}, | |
700 | {"options", no_argument, NULL, 'o'}, | |
701 | {"version", no_argument, NULL, 'V'}, | |
67d9b930 | 702 | DAEMON_LONG_OPTIONS, |
ac0630a2 RB |
703 | VLOG_LONG_OPTIONS, |
704 | STREAM_SSL_LONG_OPTIONS, | |
705 | {NULL, 0, NULL, 0}, | |
706 | }; | |
707 | char *short_options = ovs_cmdl_long_options_to_short_options(long_options); | |
708 | ||
709 | for (;;) { | |
710 | int c; | |
711 | ||
712 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
713 | if (c == -1) { | |
714 | break; | |
715 | } | |
716 | ||
717 | switch (c) { | |
67d9b930 | 718 | DAEMON_OPTION_HANDLERS; |
ac0630a2 RB |
719 | VLOG_OPTION_HANDLERS; |
720 | STREAM_SSL_OPTION_HANDLERS; | |
721 | ||
722 | case 'd': | |
ec78987f | 723 | ovnsb_db = optarg; |
ac0630a2 RB |
724 | break; |
725 | ||
726 | case 'D': | |
727 | ovnnb_db = optarg; | |
728 | break; | |
729 | ||
730 | case 'h': | |
731 | usage(); | |
732 | exit(EXIT_SUCCESS); | |
733 | ||
734 | case 'o': | |
735 | ovs_cmdl_print_options(long_options); | |
736 | exit(EXIT_SUCCESS); | |
737 | ||
738 | case 'V': | |
739 | ovs_print_version(0, 0); | |
740 | exit(EXIT_SUCCESS); | |
741 | ||
742 | default: | |
743 | break; | |
744 | } | |
745 | } | |
746 | ||
ec78987f JP |
747 | if (!ovnsb_db) { |
748 | ovnsb_db = default_db(); | |
ac0630a2 RB |
749 | } |
750 | ||
751 | if (!ovnnb_db) { | |
752 | ovnnb_db = default_db(); | |
753 | } | |
754 | ||
755 | free(short_options); | |
756 | } | |
757 | ||
758 | int | |
759 | main(int argc, char *argv[]) | |
760 | { | |
761 | extern struct vlog_module VLM_reconnect; | |
ec78987f | 762 | struct ovsdb_idl *ovnnb_idl, *ovnsb_idl; |
ac0630a2 RB |
763 | unsigned int ovnnb_seqno, ovn_seqno; |
764 | int res = EXIT_SUCCESS; | |
2e2762d4 | 765 | struct northd_context ctx = { |
3c78b3ca | 766 | .ovnsb_txn = NULL, |
f93818dd RB |
767 | }; |
768 | bool ovnnb_changes_pending = false; | |
769 | bool ovn_changes_pending = false; | |
ac0630a2 RB |
770 | |
771 | fatal_ignore_sigpipe(); | |
772 | set_program_name(argv[0]); | |
773 | vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN); | |
774 | vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN); | |
775 | parse_options(argc, argv); | |
67d9b930 RB |
776 | |
777 | daemonize(); | |
778 | ||
ac0630a2 | 779 | nbrec_init(); |
ec78987f | 780 | sbrec_init(); |
ac0630a2 RB |
781 | |
782 | /* We want to detect all changes to the ovn-nb db. */ | |
f93818dd RB |
783 | ctx.ovnnb_idl = ovnnb_idl = ovsdb_idl_create(ovnnb_db, |
784 | &nbrec_idl_class, true, true); | |
ac0630a2 | 785 | |
91ae2065 | 786 | /* There is only a small subset of changes to the ovn-sb db that ovn-northd |
a0149f47 | 787 | * has to care about, so we'll enable monitoring those directly. */ |
ec78987f JP |
788 | ctx.ovnsb_idl = ovnsb_idl = ovsdb_idl_create(ovnsb_db, |
789 | &sbrec_idl_class, false, true); | |
e387e3e8 BP |
790 | ovsdb_idl_add_table(ovnsb_idl, &sbrec_table_binding); |
791 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_port); | |
792 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_chassis); | |
793 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_mac); | |
794 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tag); | |
795 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_parent_port); | |
796 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_logical_datapath); | |
797 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_binding_col_tunnel_key); | |
bd39395f BP |
798 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_logical_datapath); |
799 | ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_logical_datapath); | |
800 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_table_id); | |
801 | ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_table_id); | |
802 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_priority); | |
803 | ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_priority); | |
804 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_match); | |
805 | ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_match); | |
806 | ovsdb_idl_add_column(ovnsb_idl, &sbrec_pipeline_col_actions); | |
807 | ovsdb_idl_omit_alert(ovnsb_idl, &sbrec_pipeline_col_actions); | |
ac0630a2 RB |
808 | |
809 | /* | |
810 | * The loop here just runs the IDL in a loop waiting for the seqno to | |
811 | * change, which indicates that the contents of the db have changed. | |
812 | * | |
a0149f47 JP |
813 | * If the contents of the ovn-nb db change, the mappings to the ovn-sb |
814 | * db must be recalculated. | |
ac0630a2 | 815 | * |
a0149f47 | 816 | * If the contents of the ovn-sb db change, it means the 'up' state of |
91ae2065 | 817 | * a port may have changed, as that's the only type of change ovn-northd is |
a0149f47 | 818 | * watching for. |
ac0630a2 RB |
819 | */ |
820 | ||
821 | ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl); | |
ec78987f | 822 | ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl); |
ac0630a2 RB |
823 | for (;;) { |
824 | ovsdb_idl_run(ovnnb_idl); | |
ec78987f | 825 | ovsdb_idl_run(ovnsb_idl); |
ac0630a2 RB |
826 | |
827 | if (!ovsdb_idl_is_alive(ovnnb_idl)) { | |
828 | int retval = ovsdb_idl_get_last_error(ovnnb_idl); | |
829 | VLOG_ERR("%s: database connection failed (%s)", | |
830 | ovnnb_db, ovs_retval_to_string(retval)); | |
831 | res = EXIT_FAILURE; | |
832 | break; | |
833 | } | |
834 | ||
ec78987f JP |
835 | if (!ovsdb_idl_is_alive(ovnsb_idl)) { |
836 | int retval = ovsdb_idl_get_last_error(ovnsb_idl); | |
ac0630a2 | 837 | VLOG_ERR("%s: database connection failed (%s)", |
ec78987f | 838 | ovnsb_db, ovs_retval_to_string(retval)); |
ac0630a2 RB |
839 | res = EXIT_FAILURE; |
840 | break; | |
841 | } | |
842 | ||
843 | if (ovnnb_seqno != ovsdb_idl_get_seqno(ovnnb_idl)) { | |
844 | ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl); | |
f93818dd | 845 | ovnnb_changes_pending = true; |
ac0630a2 RB |
846 | } |
847 | ||
ec78987f JP |
848 | if (ovn_seqno != ovsdb_idl_get_seqno(ovnsb_idl)) { |
849 | ovn_seqno = ovsdb_idl_get_seqno(ovnsb_idl); | |
f93818dd RB |
850 | ovn_changes_pending = true; |
851 | } | |
852 | ||
853 | /* | |
854 | * If there are any pending changes, we delay recalculating the | |
855 | * necessary updates until after an existing transaction finishes. | |
91ae2065 RB |
856 | * This avoids the possibility of rapid updates causing ovn-northd to |
857 | * never be able to successfully make the corresponding updates to the | |
858 | * other db. Instead, pending changes are batched up until the next | |
859 | * time we get a chance to calculate the new state and apply it. | |
f93818dd RB |
860 | */ |
861 | ||
3c78b3ca | 862 | if (ovnnb_changes_pending && !ctx.ovnsb_txn) { |
f93818dd RB |
863 | /* |
864 | * The OVN-nb db contents have changed, so create a transaction for | |
a0149f47 | 865 | * updating the OVN-sb DB. |
f93818dd | 866 | */ |
3c78b3ca | 867 | ctx.ovnsb_txn = ovsdb_idl_txn_create(ctx.ovnsb_idl); |
5da82071 | 868 | ovsdb_idl_txn_add_comment(ctx.ovnsb_txn, |
91ae2065 | 869 | "ovn-northd: northbound db changed"); |
f93818dd RB |
870 | ovnnb_db_changed(&ctx); |
871 | ovnnb_changes_pending = false; | |
872 | } | |
873 | ||
874 | if (ovn_changes_pending && !ctx.ovnnb_txn) { | |
875 | /* | |
a0149f47 | 876 | * The OVN-sb db contents have changed, so create a transaction for |
f93818dd RB |
877 | * updating the northbound DB. |
878 | */ | |
879 | ctx.ovnnb_txn = ovsdb_idl_txn_create(ctx.ovnnb_idl); | |
5da82071 | 880 | ovsdb_idl_txn_add_comment(ctx.ovnnb_txn, |
91ae2065 | 881 | "ovn-northd: southbound db changed"); |
ec78987f | 882 | ovnsb_db_changed(&ctx); |
f93818dd RB |
883 | ovn_changes_pending = false; |
884 | } | |
885 | ||
886 | if (ctx.ovnnb_txn) { | |
887 | enum ovsdb_idl_txn_status txn_status; | |
888 | txn_status = ovsdb_idl_txn_commit(ctx.ovnnb_txn); | |
889 | switch (txn_status) { | |
890 | case TXN_UNCOMMITTED: | |
891 | case TXN_INCOMPLETE: | |
892 | /* Come back around and try to commit this transaction again */ | |
893 | break; | |
894 | case TXN_ABORTED: | |
895 | case TXN_TRY_AGAIN: | |
896 | case TXN_NOT_LOCKED: | |
897 | case TXN_ERROR: | |
898 | /* Something went wrong, so try creating a new transaction. */ | |
899 | ovn_changes_pending = true; | |
900 | case TXN_UNCHANGED: | |
901 | case TXN_SUCCESS: | |
902 | ovsdb_idl_txn_destroy(ctx.ovnnb_txn); | |
903 | ctx.ovnnb_txn = NULL; | |
904 | } | |
905 | } | |
906 | ||
3c78b3ca | 907 | if (ctx.ovnsb_txn) { |
f93818dd | 908 | enum ovsdb_idl_txn_status txn_status; |
3c78b3ca | 909 | txn_status = ovsdb_idl_txn_commit(ctx.ovnsb_txn); |
f93818dd RB |
910 | switch (txn_status) { |
911 | case TXN_UNCOMMITTED: | |
912 | case TXN_INCOMPLETE: | |
913 | /* Come back around and try to commit this transaction again */ | |
914 | break; | |
915 | case TXN_ABORTED: | |
916 | case TXN_TRY_AGAIN: | |
917 | case TXN_NOT_LOCKED: | |
918 | case TXN_ERROR: | |
919 | /* Something went wrong, so try creating a new transaction. */ | |
920 | ovnnb_changes_pending = true; | |
921 | case TXN_UNCHANGED: | |
922 | case TXN_SUCCESS: | |
3c78b3ca JP |
923 | ovsdb_idl_txn_destroy(ctx.ovnsb_txn); |
924 | ctx.ovnsb_txn = NULL; | |
f93818dd | 925 | } |
ac0630a2 RB |
926 | } |
927 | ||
928 | if (ovnnb_seqno == ovsdb_idl_get_seqno(ovnnb_idl) && | |
ec78987f | 929 | ovn_seqno == ovsdb_idl_get_seqno(ovnsb_idl)) { |
ac0630a2 | 930 | ovsdb_idl_wait(ovnnb_idl); |
ec78987f | 931 | ovsdb_idl_wait(ovnsb_idl); |
f93818dd RB |
932 | if (ctx.ovnnb_txn) { |
933 | ovsdb_idl_txn_wait(ctx.ovnnb_txn); | |
934 | } | |
3c78b3ca JP |
935 | if (ctx.ovnsb_txn) { |
936 | ovsdb_idl_txn_wait(ctx.ovnsb_txn); | |
f93818dd | 937 | } |
ac0630a2 RB |
938 | poll_block(); |
939 | } | |
940 | } | |
941 | ||
ec78987f | 942 | ovsdb_idl_destroy(ovnsb_idl); |
ac0630a2 RB |
943 | ovsdb_idl_destroy(ovnnb_idl); |
944 | ||
945 | exit(res); | |
946 | } |