]>
Commit | Line | Data |
---|---|---|
717c7fc5 JP |
1 | /* Copyright (c) 2015 Nicira, Inc. |
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 | ||
b4e87a48 BP |
18 | #include "ovn-controller.h" |
19 | ||
717c7fc5 JP |
20 | #include <errno.h> |
21 | #include <getopt.h> | |
22 | #include <signal.h> | |
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | ||
26 | #include "command-line.h" | |
27 | #include "compiler.h" | |
28 | #include "daemon.h" | |
29 | #include "dirs.h" | |
30 | #include "openvswitch/vconn.h" | |
31 | #include "openvswitch/vlog.h" | |
e3df8838 | 32 | #include "ovn/lib/ovn-sb-idl.h" |
717c7fc5 JP |
33 | #include "poll-loop.h" |
34 | #include "fatal-signal.h" | |
35 | #include "lib/vswitch-idl.h" | |
36 | #include "smap.h" | |
37 | #include "stream.h" | |
38 | #include "stream-ssl.h" | |
39 | #include "unixctl.h" | |
40 | #include "util.h" | |
41 | ||
b4e87a48 | 42 | #include "ofctrl.h" |
e387e3e8 | 43 | #include "binding.h" |
717c7fc5 | 44 | #include "chassis.h" |
deab5e67 | 45 | #include "encaps.h" |
e71ac5cd | 46 | #include "physical.h" |
48605550 | 47 | #include "lflow.h" |
717c7fc5 JP |
48 | |
49 | VLOG_DEFINE_THIS_MODULE(main); | |
50 | ||
51 | static unixctl_cb_func ovn_controller_exit; | |
52 | ||
c758b109 JP |
53 | #define DEFAULT_BRIDGE_NAME "br-int" |
54 | ||
717c7fc5 JP |
55 | static void parse_options(int argc, char *argv[]); |
56 | OVS_NO_RETURN static void usage(void); | |
57 | ||
58 | static char *ovs_remote; | |
717c7fc5 JP |
59 | |
60 | static void | |
61 | get_initial_snapshot(struct ovsdb_idl *idl) | |
62 | { | |
63 | while (1) { | |
64 | ovsdb_idl_run(idl); | |
65 | if (ovsdb_idl_has_ever_connected(idl)) { | |
66 | return; | |
67 | } | |
68 | ovsdb_idl_wait(idl); | |
69 | poll_block(); | |
70 | } | |
71 | } | |
72 | ||
c758b109 | 73 | static const struct ovsrec_bridge * |
9e8fa2b6 | 74 | get_br_int(struct ovsdb_idl *ovs_idl) |
c758b109 | 75 | { |
9e8fa2b6 BP |
76 | const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); |
77 | if (!cfg) { | |
78 | return NULL; | |
79 | } | |
c758b109 | 80 | |
9e8fa2b6 BP |
81 | const char *br_int_name = smap_get(&cfg->external_ids, "ovn-bridge"); |
82 | if (!br_int_name) { | |
83 | br_int_name = DEFAULT_BRIDGE_NAME; | |
84 | } | |
85 | ||
86 | const struct ovsrec_bridge *br; | |
87 | OVSREC_BRIDGE_FOR_EACH (br, ovs_idl) { | |
88 | if (!strcmp(br->name, br_int_name)) { | |
c758b109 JP |
89 | return br; |
90 | } | |
91 | } | |
92 | ||
37c7a694 | 93 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); |
9e8fa2b6 | 94 | VLOG_WARN_RL(&rl, "%s: integration bridge does not exist", br_int_name); |
c758b109 JP |
95 | return NULL; |
96 | } | |
97 | ||
30a4256f BP |
98 | static const char * |
99 | get_chassis_id(const struct ovsdb_idl *ovs_idl) | |
100 | { | |
101 | const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); | |
102 | return cfg ? smap_get(&cfg->external_ids, "system-id") : NULL; | |
103 | } | |
104 | ||
9e8fa2b6 BP |
105 | /* Retrieves the OVN Southbound remote location from the |
106 | * "external-ids:ovn-remote" key in 'ovs_idl' and returns a copy of it. | |
ba88f959 | 107 | * |
9e8fa2b6 BP |
108 | * XXX ovn-controller does not support this changing mid-run, but that should |
109 | * be addressed later. */ | |
110 | static char * | |
111 | get_ovnsb_remote(struct ovsdb_idl *ovs_idl) | |
717c7fc5 | 112 | { |
9326534c | 113 | while (1) { |
9e8fa2b6 BP |
114 | ovsdb_idl_run(ovs_idl); |
115 | ||
116 | const struct ovsrec_open_vswitch *cfg | |
117 | = ovsrec_open_vswitch_first(ovs_idl); | |
118 | if (cfg) { | |
119 | const char *remote = smap_get(&cfg->external_ids, "ovn-remote"); | |
120 | if (remote) { | |
121 | return xstrdup(remote); | |
122 | } | |
717c7fc5 JP |
123 | } |
124 | ||
9e8fa2b6 BP |
125 | VLOG_INFO("OVN OVSDB remote not specified. Waiting..."); |
126 | ovsdb_idl_wait(ovs_idl); | |
717c7fc5 JP |
127 | poll_block(); |
128 | } | |
717c7fc5 JP |
129 | } |
130 | ||
f1fd7657 BP |
131 | struct idl_loop { |
132 | struct ovsdb_idl *idl; | |
133 | unsigned int skip_seqno; | |
134 | ||
135 | struct ovsdb_idl_txn *committing_txn; | |
136 | unsigned int precommit_seqno; | |
137 | ||
138 | struct ovsdb_idl_txn *open_txn; | |
139 | }; | |
140 | ||
141 | #define IDL_LOOP_INITIALIZER(IDL) { .idl = (IDL) } | |
142 | ||
143 | static void | |
144 | idl_loop_destroy(struct idl_loop *loop) | |
145 | { | |
146 | if (loop) { | |
147 | ovsdb_idl_destroy(loop->idl); | |
148 | } | |
149 | } | |
150 | ||
151 | static struct ovsdb_idl_txn * | |
152 | idl_loop_run(struct idl_loop *loop) | |
153 | { | |
154 | ovsdb_idl_run(loop->idl); | |
155 | loop->open_txn = (loop->committing_txn | |
156 | || ovsdb_idl_get_seqno(loop->idl) == loop->skip_seqno | |
157 | ? NULL | |
158 | : ovsdb_idl_txn_create(loop->idl)); | |
159 | return loop->open_txn; | |
160 | } | |
161 | ||
162 | static void | |
163 | idl_loop_commit_and_wait(struct idl_loop *loop) | |
164 | { | |
165 | if (loop->open_txn) { | |
166 | loop->committing_txn = loop->open_txn; | |
167 | loop->open_txn = NULL; | |
168 | ||
169 | loop->precommit_seqno = ovsdb_idl_get_seqno(loop->idl); | |
170 | } | |
171 | ||
172 | struct ovsdb_idl_txn *txn = loop->committing_txn; | |
173 | if (txn) { | |
174 | enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(txn); | |
f05a84ae BP |
175 | if (status != TXN_INCOMPLETE) { |
176 | switch (status) { | |
177 | case TXN_TRY_AGAIN: | |
178 | /* We want to re-evaluate the database when it's changed from | |
179 | * the contents that it had when we started the commit. (That | |
180 | * might have already happened.) */ | |
181 | loop->skip_seqno = loop->precommit_seqno; | |
182 | if (ovsdb_idl_get_seqno(loop->idl) != loop->skip_seqno) { | |
183 | poll_immediate_wake(); | |
184 | } | |
185 | break; | |
186 | ||
187 | case TXN_SUCCESS: | |
188 | /* If the database has already changed since we started the | |
189 | * commit, re-evaluate it immediately to avoid missing a change | |
190 | * for a while. */ | |
191 | if (ovsdb_idl_get_seqno(loop->idl) != loop->precommit_seqno) { | |
192 | poll_immediate_wake(); | |
193 | } | |
194 | break; | |
195 | ||
196 | case TXN_UNCHANGED: | |
197 | case TXN_ABORTED: | |
198 | case TXN_NOT_LOCKED: | |
199 | case TXN_ERROR: | |
200 | break; | |
201 | ||
202 | case TXN_UNCOMMITTED: | |
203 | case TXN_INCOMPLETE: | |
204 | OVS_NOT_REACHED(); | |
205 | ||
f1fd7657 | 206 | } |
f1fd7657 BP |
207 | ovsdb_idl_txn_destroy(txn); |
208 | loop->committing_txn = NULL; | |
f1fd7657 BP |
209 | } |
210 | } | |
211 | ||
212 | ovsdb_idl_wait(loop->idl); | |
213 | } | |
214 | ||
717c7fc5 JP |
215 | int |
216 | main(int argc, char *argv[]) | |
217 | { | |
218 | struct unixctl_server *unixctl; | |
717c7fc5 JP |
219 | bool exiting; |
220 | int retval; | |
221 | ||
222 | ovs_cmdl_proctitle_init(argc, argv); | |
223 | set_program_name(argv[0]); | |
224 | parse_options(argc, argv); | |
225 | fatal_ignore_sigpipe(); | |
226 | ||
227 | daemonize_start(); | |
228 | ||
229 | retval = unixctl_server_create(NULL, &unixctl); | |
230 | if (retval) { | |
231 | exit(EXIT_FAILURE); | |
232 | } | |
233 | unixctl_command_register("exit", "", 0, 0, ovn_controller_exit, &exiting); | |
234 | ||
235 | daemonize_complete(); | |
236 | ||
237 | ovsrec_init(); | |
238 | sbrec_init(); | |
239 | ||
b4e87a48 | 240 | ofctrl_init(); |
48605550 | 241 | lflow_init(); |
b4e87a48 | 242 | |
717c7fc5 JP |
243 | /* Connect to OVS OVSDB instance. We do not monitor all tables by |
244 | * default, so modules must register their interest explicitly. */ | |
710164bc BP |
245 | struct idl_loop ovs_idl_loop = IDL_LOOP_INITIALIZER( |
246 | ovsdb_idl_create(ovs_remote, &ovsrec_idl_class, false, true)); | |
247 | ovsdb_idl_add_table(ovs_idl_loop.idl, &ovsrec_table_open_vswitch); | |
248 | ovsdb_idl_add_column(ovs_idl_loop.idl, | |
249 | &ovsrec_open_vswitch_col_external_ids); | |
250 | chassis_register_ovs_idl(ovs_idl_loop.idl); | |
251 | encaps_register_ovs_idl(ovs_idl_loop.idl); | |
252 | binding_register_ovs_idl(ovs_idl_loop.idl); | |
253 | physical_register_ovs_idl(ovs_idl_loop.idl); | |
254 | get_initial_snapshot(ovs_idl_loop.idl); | |
255 | ||
256 | /* Connect to OVN SB database. */ | |
257 | char *ovnsb_remote = get_ovnsb_remote(ovs_idl_loop.idl); | |
258 | struct idl_loop ovnsb_idl_loop = IDL_LOOP_INITIALIZER( | |
259 | ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true)); | |
260 | get_initial_snapshot(ovnsb_idl_loop.idl); | |
f1fd7657 BP |
261 | |
262 | /* Main loop. */ | |
717c7fc5 JP |
263 | exiting = false; |
264 | while (!exiting) { | |
710164bc BP |
265 | struct controller_ctx ctx = { |
266 | .ovs_idl = ovs_idl_loop.idl, | |
267 | .ovs_idl_txn = idl_loop_run(&ovs_idl_loop), | |
268 | .ovnsb_idl = ovnsb_idl_loop.idl, | |
269 | .ovnsb_idl_txn = idl_loop_run(&ovnsb_idl_loop), | |
270 | }; | |
717c7fc5 | 271 | |
9e8fa2b6 | 272 | const struct ovsrec_bridge *br_int = get_br_int(ctx.ovs_idl); |
30a4256f | 273 | const char *chassis_id = get_chassis_id(ctx.ovs_idl); |
c758b109 | 274 | |
30a4256f BP |
275 | if (chassis_id) { |
276 | chassis_run(&ctx, chassis_id); | |
277 | encaps_run(&ctx, br_int, chassis_id); | |
278 | binding_run(&ctx, br_int, chassis_id); | |
279 | } | |
761fd08f | 280 | |
37c7a694 | 281 | if (br_int) { |
5868eb24 | 282 | enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int); |
1b05a9d3 | 283 | |
37c7a694 | 284 | struct hmap flow_table = HMAP_INITIALIZER(&flow_table); |
48605550 | 285 | lflow_run(&ctx, &flow_table); |
30a4256f | 286 | if (chassis_id) { |
5868eb24 BP |
287 | physical_run(&ctx, mff_ovn_geneve, |
288 | br_int, chassis_id, &flow_table); | |
30a4256f | 289 | } |
1b05a9d3 | 290 | ofctrl_put(&flow_table); |
37c7a694 BP |
291 | hmap_destroy(&flow_table); |
292 | } | |
761fd08f | 293 | |
717c7fc5 JP |
294 | unixctl_server_run(unixctl); |
295 | ||
296 | unixctl_server_wait(unixctl); | |
297 | if (exiting) { | |
298 | poll_immediate_wake(); | |
299 | } | |
300 | ||
f1fd7657 BP |
301 | idl_loop_commit_and_wait(&ovnsb_idl_loop); |
302 | idl_loop_commit_and_wait(&ovs_idl_loop); | |
303 | ||
37c7a694 BP |
304 | if (br_int) { |
305 | ofctrl_wait(); | |
306 | } | |
717c7fc5 JP |
307 | poll_block(); |
308 | } | |
309 | ||
f1fd7657 BP |
310 | /* It's time to exit. Clean up the databases. */ |
311 | bool done = false; | |
312 | while (!done) { | |
710164bc BP |
313 | struct controller_ctx ctx = { |
314 | .ovs_idl = ovs_idl_loop.idl, | |
315 | .ovs_idl_txn = idl_loop_run(&ovs_idl_loop), | |
316 | .ovnsb_idl = ovnsb_idl_loop.idl, | |
317 | .ovnsb_idl_txn = idl_loop_run(&ovnsb_idl_loop), | |
318 | }; | |
f1fd7657 | 319 | |
9e8fa2b6 | 320 | const struct ovsrec_bridge *br_int = get_br_int(ctx.ovs_idl); |
30a4256f | 321 | const char *chassis_id = get_chassis_id(ctx.ovs_idl); |
f1fd7657 | 322 | |
deab5e67 BP |
323 | /* Run all of the cleanup functions, even if one of them returns false. |
324 | * We're done if all of them return true. */ | |
4acc496e BP |
325 | done = binding_cleanup(&ctx, chassis_id); |
326 | done = chassis_cleanup(&ctx, chassis_id) && done; | |
deab5e67 | 327 | done = encaps_cleanup(&ctx, br_int) && done; |
f1fd7657 BP |
328 | if (done) { |
329 | poll_immediate_wake(); | |
330 | } | |
331 | ||
332 | idl_loop_commit_and_wait(&ovnsb_idl_loop); | |
333 | idl_loop_commit_and_wait(&ovs_idl_loop); | |
334 | poll_block(); | |
335 | } | |
336 | ||
717c7fc5 | 337 | unixctl_server_destroy(unixctl); |
48605550 | 338 | lflow_destroy(); |
b4e87a48 | 339 | ofctrl_destroy(); |
717c7fc5 | 340 | |
f1fd7657 BP |
341 | idl_loop_destroy(&ovs_idl_loop); |
342 | idl_loop_destroy(&ovnsb_idl_loop); | |
717c7fc5 | 343 | |
07c747d0 RB |
344 | free(ovnsb_remote); |
345 | free(ovs_remote); | |
71bf929e | 346 | |
717c7fc5 JP |
347 | exit(retval); |
348 | } | |
349 | ||
350 | static void | |
351 | parse_options(int argc, char *argv[]) | |
352 | { | |
353 | enum { | |
354 | OPT_PEER_CA_CERT = UCHAR_MAX + 1, | |
355 | VLOG_OPTION_ENUMS, | |
356 | DAEMON_OPTION_ENUMS | |
357 | }; | |
358 | ||
359 | static struct option long_options[] = { | |
360 | {"help", no_argument, NULL, 'h'}, | |
361 | {"version", no_argument, NULL, 'V'}, | |
362 | VLOG_LONG_OPTIONS, | |
363 | DAEMON_LONG_OPTIONS, | |
364 | STREAM_SSL_LONG_OPTIONS, | |
365 | {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT}, | |
366 | {NULL, 0, NULL, 0} | |
367 | }; | |
368 | char *short_options = ovs_cmdl_long_options_to_short_options(long_options); | |
369 | ||
370 | for (;;) { | |
371 | int c; | |
372 | ||
373 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
374 | if (c == -1) { | |
375 | break; | |
376 | } | |
377 | ||
378 | switch (c) { | |
379 | case 'h': | |
380 | usage(); | |
381 | ||
382 | case 'V': | |
383 | ovs_print_version(OFP13_VERSION, OFP13_VERSION); | |
384 | exit(EXIT_SUCCESS); | |
385 | ||
386 | VLOG_OPTION_HANDLERS | |
387 | DAEMON_OPTION_HANDLERS | |
388 | STREAM_SSL_OPTION_HANDLERS | |
389 | ||
390 | case OPT_PEER_CA_CERT: | |
391 | stream_ssl_set_peer_ca_cert_file(optarg); | |
392 | break; | |
393 | ||
394 | case '?': | |
395 | exit(EXIT_FAILURE); | |
396 | ||
397 | default: | |
398 | abort(); | |
399 | } | |
400 | } | |
401 | free(short_options); | |
402 | ||
403 | argc -= optind; | |
404 | argv += optind; | |
405 | ||
406 | if (argc == 0) { | |
407 | ovs_remote = xasprintf("unix:%s/db.sock", ovs_rundir()); | |
408 | } else if (argc == 1) { | |
07c747d0 | 409 | ovs_remote = xstrdup(argv[0]); |
717c7fc5 JP |
410 | } else { |
411 | VLOG_FATAL("exactly zero or one non-option argument required; " | |
412 | "use --help for usage"); | |
413 | } | |
414 | } | |
415 | ||
416 | static void | |
417 | usage(void) | |
418 | { | |
419 | printf("%s: OVN controller\n" | |
420 | "usage %s [OPTIONS] [OVS-DATABASE]\n" | |
421 | "where OVS-DATABASE is a socket on which the OVS OVSDB server is listening.\n", | |
422 | program_name, program_name); | |
423 | stream_usage("OVS-DATABASE", true, false, false); | |
424 | daemon_usage(); | |
425 | vlog_usage(); | |
426 | printf("\nOther options:\n" | |
427 | " -h, --help display this help message\n" | |
428 | " -V, --version display version information\n"); | |
429 | exit(EXIT_SUCCESS); | |
430 | } | |
431 | ||
432 | static void | |
433 | ovn_controller_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, | |
434 | const char *argv[] OVS_UNUSED, void *exiting_) | |
435 | { | |
436 | bool *exiting = exiting_; | |
437 | *exiting = true; | |
438 | ||
439 | unixctl_command_reply(conn, NULL); | |
440 | } |