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