leak-checker: Remove because it cannot be made thread-safe.
[sliver-openvswitch.git] / ovsdb / ovsdb-server.c
index 69548c2..b31560b 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
+/* Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
 
 #include <config.h>
 
-#include <assert.h>
 #include <errno.h>
 #include <getopt.h>
+#include <inttypes.h>
 #include <signal.h>
 #include <unistd.h>
 
@@ -32,7 +32,6 @@
 #include "json.h"
 #include "jsonrpc.h"
 #include "jsonrpc-server.h"
-#include "leak-checker.h"
 #include "list.h"
 #include "memory.h"
 #include "ovsdb.h"
@@ -77,6 +76,21 @@ static unixctl_cb_func ovsdb_server_exit;
 static unixctl_cb_func ovsdb_server_compact;
 static unixctl_cb_func ovsdb_server_reconnect;
 
+struct add_remote_aux {
+    struct sset *remotes;
+    struct db *dbs;
+    size_t n_dbs;
+    FILE *config_tmpfile;
+};
+static unixctl_cb_func ovsdb_server_add_remote;
+
+struct remove_remote_aux {
+    struct sset *remotes;
+    FILE *config_tmpfile;
+};
+static unixctl_cb_func ovsdb_server_remove_remote;
+static unixctl_cb_func ovsdb_server_list_remotes;
+
 static void parse_options(int *argc, char **argvp[],
                           struct sset *remotes, char **unixctl_pathp,
                           char **run_command);
@@ -90,6 +104,9 @@ static void update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
                                  const struct sset *remotes,
                                  struct db dbs[], size_t n_dbs);
 
+static void save_config(FILE *config_file, const struct sset *);
+static void load_config(FILE *config_file, struct sset *);
+
 int
 main(int argc, char *argv[])
 {
@@ -102,6 +119,9 @@ main(int argc, char *argv[])
     bool exiting;
     int retval;
     long long int status_timer = LLONG_MIN;
+    struct add_remote_aux add_remote_aux;
+    struct remove_remote_aux remove_remote_aux;
+    FILE *config_tmpfile;
 
     struct db *dbs;
     int n_dbs;
@@ -115,8 +135,22 @@ main(int argc, char *argv[])
 
     parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command);
 
+    /* Create and initialize 'config_tmpfile' as a temporary file to hold
+     * ovsdb-server's most basic configuration, and then save our initial
+     * configuration to it.  When --monitor is used, this preserves the effects
+     * of ovs-appctl commands such as ovsdb-server/add-remote (which saves the
+     * new configuration) across crashes. */
+    config_tmpfile = tmpfile();
+    if (!config_tmpfile) {
+        ovs_fatal(errno, "failed to create temporary file");
+    }
+    save_config(config_tmpfile, &remotes);
+
     daemonize_start();
 
+    /* Load the saved config. */
+    load_config(config_tmpfile, &remotes);
+
     n_dbs = MAX(1, argc);
     dbs = xcalloc(n_dbs + 1, sizeof *dbs);
     if (argc > 0) {
@@ -159,7 +193,7 @@ main(int argc, char *argv[])
         run_argv[2] = run_command;
         run_argv[3] = NULL;
 
-        retval = process_start(run_argv, NULL, 0, NULL, 0, &run_process);
+        retval = process_start(run_argv, &run_process);
         if (retval) {
             ovs_fatal(retval, "%s: process failed to start", run_command);
         }
@@ -182,6 +216,21 @@ main(int argc, char *argv[])
     unixctl_command_register("ovsdb-server/reconnect", "", 0, 0,
                              ovsdb_server_reconnect, jsonrpc);
 
+    add_remote_aux.remotes = &remotes;
+    add_remote_aux.dbs = dbs;
+    add_remote_aux.n_dbs = n_dbs;
+    add_remote_aux.config_tmpfile = config_tmpfile;
+    unixctl_command_register("ovsdb-server/add-remote", "REMOTE", 1, 1,
+                             ovsdb_server_add_remote, &add_remote_aux);
+
+    remove_remote_aux.remotes = &remotes;
+    remove_remote_aux.config_tmpfile = config_tmpfile;
+    unixctl_command_register("ovsdb-server/remove-remote", "REMOTE", 1, 1,
+                             ovsdb_server_remove_remote, &remove_remote_aux);
+
+    unixctl_command_register("ovsdb-server/list-remotes", "", 0, 0,
+                             ovsdb_server_list_remotes, &remotes);
+
     exiting = false;
     while (!exiting) {
         int i;
@@ -199,15 +248,22 @@ main(int argc, char *argv[])
             simap_destroy(&usage);
         }
 
+        /* Run unixctl_server_run() before reconfigure_from_db() because
+         * ovsdb-server/add-remote and ovsdb-server/remove-remote can change
+         * the set of remotes that reconfigure_from_db() uses. */
+        unixctl_server_run(unixctl);
+
         reconfigure_from_db(jsonrpc, dbs, n_dbs, &remotes);
         ovsdb_jsonrpc_server_run(jsonrpc);
-        unixctl_server_run(unixctl);
 
         for (i = 0; i < n_dbs; i++) {
             ovsdb_trigger_run(dbs[i].db, time_msec());
         }
-        if (run_process && process_exited(run_process)) {
-            exiting = true;
+        if (run_process) {
+            process_run();
+            if (process_exited(run_process)) {
+                exiting = true;
+            }
         }
 
         /* update Manager status(es) every 5 seconds */
@@ -263,12 +319,12 @@ find_db(const struct db dbs[], size_t n_dbs, const char *db_name)
     return NULL;
 }
 
-static void
-parse_db_column(const struct db dbs[], size_t n_dbs,
-                const char *name_,
-                const struct db **dbp,
-                const struct ovsdb_table **tablep,
-                const struct ovsdb_column **columnp)
+static char * WARN_UNUSED_RESULT
+parse_db_column__(const struct db dbs[], size_t n_dbs,
+                  const char *name_, char *name,
+                  const struct db **dbp,
+                  const struct ovsdb_table **tablep,
+                  const struct ovsdb_column **columnp)
 {
     const char *table_name, *column_name;
     const struct ovsdb_column *column;
@@ -276,15 +332,17 @@ parse_db_column(const struct db dbs[], size_t n_dbs,
     const char *tokens[3];
     char *save_ptr = NULL;
     const struct db *db;
-    char *name;
 
-    name = xstrdup(name_);
+    *dbp = NULL;
+    *tablep = NULL;
+    *columnp = NULL;
+
     strtok_r(name, ":", &save_ptr); /* "db:" */
     tokens[0] = strtok_r(NULL, ",", &save_ptr);
     tokens[1] = strtok_r(NULL, ",", &save_ptr);
     tokens[2] = strtok_r(NULL, ",", &save_ptr);
     if (!tokens[0] || !tokens[1]) {
-        ovs_fatal(0, "\"%s\": invalid syntax", name_);
+        return xasprintf("\"%s\": invalid syntax", name_);
     }
     if (tokens[2]) {
         const char *db_name = tokens[0];
@@ -293,12 +351,13 @@ parse_db_column(const struct db dbs[], size_t n_dbs,
 
         db = find_db(dbs, n_dbs, tokens[0]);
         if (!db) {
-            ovs_fatal(0, "\"%s\": no database named %s", name_, db_name);
+            return xasprintf("\"%s\": no database named %s", name_, db_name);
         }
     } else {
         if (n_dbs > 1) {
-            ovs_fatal(0, "\"%s\": database name must be specified (because "
-                      "multiple databases are configured)", name_);
+            return xasprintf("\"%s\": database name must be specified "
+                             "(because multiple databases are configured)",
+                             name_);
         }
 
         table_name = tokens[0];
@@ -308,44 +367,61 @@ parse_db_column(const struct db dbs[], size_t n_dbs,
 
     table = ovsdb_get_table(db->db, table_name);
     if (!table) {
-        ovs_fatal(0, "\"%s\": no table named %s", name_, table_name);
+        return xasprintf("\"%s\": no table named %s", name_, table_name);
     }
 
     column = ovsdb_table_schema_get_column(table->schema, column_name);
     if (!column) {
-        ovs_fatal(0, "\"%s\": table \"%s\" has no column \"%s\"",
-                  name_, table_name, column_name);
+        return xasprintf("\"%s\": table \"%s\" has no column \"%s\"",
+                         name_, table_name, column_name);
     }
-    free(name);
 
     *dbp = db;
     *columnp = column;
     *tablep = table;
+    return NULL;
 }
 
-static void
+/* Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. */
+static char * WARN_UNUSED_RESULT
+parse_db_column(const struct db dbs[], size_t n_dbs,
+                const char *name_,
+                const struct db **dbp,
+                const struct ovsdb_table **tablep,
+                const struct ovsdb_column **columnp)
+{
+    char *name = xstrdup(name_);
+    char *retval = parse_db_column__(dbs, n_dbs, name_, name,
+                                     dbp, tablep, columnp);
+    free(name);
+    return retval;
+}
+
+/* Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error. */
+static char * WARN_UNUSED_RESULT
 parse_db_string_column(const struct db dbs[], size_t n_dbs,
                        const char *name,
                        const struct db **dbp,
                        const struct ovsdb_table **tablep,
                        const struct ovsdb_column **columnp)
 {
-    const struct ovsdb_column *column;
-    const struct ovsdb_table *table;
-    const struct db *db;
+    char *retval;
 
-    parse_db_column(dbs, n_dbs, name, &db, &table, &column);
+    retval = parse_db_column(dbs, n_dbs, name, dbp, tablep, columnp);
+    if (retval) {
+        return retval;
+    }
 
-    if (column->type.key.type != OVSDB_TYPE_STRING
-        || column->type.value.type != OVSDB_TYPE_VOID) {
-        ovs_fatal(0, "\"%s\": table \"%s\" column \"%s\" is "
-                  "not string or set of strings",
-                  name, table->schema->name, column->name);
+    if ((*columnp)->type.key.type != OVSDB_TYPE_STRING
+        || (*columnp)->type.value.type != OVSDB_TYPE_VOID) {
+        return xasprintf("\"%s\": table \"%s\" column \"%s\" is "
+                         "not string or set of strings",
+                         name, (*tablep)->schema->name, (*columnp)->name);
     }
 
-    *dbp = db;
-    *columnp = column;
-    *tablep = table;
+    return NULL;
 }
 
 static OVS_UNUSED const char *
@@ -358,8 +434,13 @@ query_db_string(const struct db dbs[], size_t n_dbs, const char *name)
         const struct ovsdb_table *table;
         const struct ovsdb_row *row;
         const struct db *db;
+        char *retval;
 
-        parse_db_string_column(dbs, n_dbs, name, &db, &table, &column);
+        retval = parse_db_string_column(dbs, n_dbs, name,
+                                        &db, &table, &column);
+        if (retval) {
+            ovs_fatal(0, "%s", retval);
+        }
 
         HMAP_FOR_EACH (row, hmap_node, &table->rows) {
             const struct ovsdb_datum *datum;
@@ -523,6 +604,10 @@ write_string_string_column(struct ovsdb_row *row, const char *column_name,
     datum = get_datum(row, column_name, OVSDB_TYPE_STRING, OVSDB_TYPE_STRING,
                       UINT_MAX);
     if (!datum) {
+        for (i = 0; i < n; i++) {
+            free(keys[i]);
+            free(values[i]);
+        }
         return;
     }
 
@@ -585,8 +670,12 @@ query_db_remotes(const char *name, const struct db dbs[], size_t n_dbs,
     const struct ovsdb_table *table;
     const struct ovsdb_row *row;
     const struct db *db;
+    char *retval;
 
-    parse_db_column(dbs, n_dbs, name, &db, &table, &column);
+    retval = parse_db_column(dbs, n_dbs, name, &db, &table, &column);
+    if (retval) {
+        ovs_fatal(0, "%s", retval);
+    }
 
     if (column->type.key.type == OVSDB_TYPE_STRING
         && column->type.value.type == OVSDB_TYPE_VOID) {
@@ -627,7 +716,7 @@ update_remote_row(const struct ovsdb_row *row, struct ovsdb_txn *txn,
     struct ovsdb_jsonrpc_remote_status status;
     struct ovsdb_row *rw_row;
     const char *target;
-    char *keys[8], *values[8];
+    char *keys[9], *values[9];
     size_t n = 0;
 
     /* Get the "target" (protocol/host/port) spec. */
@@ -674,6 +763,10 @@ update_remote_row(const struct ovsdb_row *row, struct ovsdb_txn *txn,
         keys[n] = xstrdup("n_connections");
         values[n++] = xasprintf("%d", status.n_connections);
     }
+    if (status.bound_port != htons(0)) {
+        keys[n] = xstrdup("bound_port");
+        values[n++] = xasprintf("%"PRIu16, ntohs(status.bound_port));
+    }
     write_string_string_column(rw_row, "status", keys, values, n);
 
     ovsdb_jsonrpc_server_free_remote_status(&status);
@@ -688,12 +781,16 @@ update_remote_rows(const struct db dbs[], size_t n_dbs,
     const struct ovsdb_column *column;
     const struct ovsdb_row *row;
     const struct db *db;
+    char *retval;
 
     if (strncmp("db:", remote_name, 3)) {
         return;
     }
 
-    parse_db_column(dbs, n_dbs, remote_name, &db, &table, &column);
+    retval = parse_db_column(dbs, n_dbs, remote_name, &db, &table, &column);
+    if (retval) {
+        ovs_fatal(0, "%s", retval);
+    }
 
     if (column->type.key.type != OVSDB_TYPE_UUID
         || !column->type.key.u.uuid.refTable
@@ -835,6 +932,75 @@ ovsdb_server_reconnect(struct unixctl_conn *conn, int argc OVS_UNUSED,
     unixctl_command_reply(conn, NULL);
 }
 
+/* "ovsdb-server/add-remote REMOTE": adds REMOTE to the set of remotes that
+ * ovsdb-server services. */
+static void
+ovsdb_server_add_remote(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                        const char *argv[], void *aux_)
+{
+    struct add_remote_aux *aux = aux_;
+    const char *remote = argv[1];
+
+    const struct ovsdb_column *column;
+    const struct ovsdb_table *table;
+    const struct db *db;
+    char *retval;
+
+    retval = (strncmp("db:", remote, 3)
+              ? NULL
+              : parse_db_column(aux->dbs, aux->n_dbs, remote,
+                                &db, &table, &column));
+    if (!retval) {
+        if (sset_add(aux->remotes, remote)) {
+            save_config(aux->config_tmpfile, aux->remotes);
+        }
+        unixctl_command_reply(conn, NULL);
+    } else {
+        unixctl_command_reply_error(conn, retval);
+        free(retval);
+    }
+}
+
+/* "ovsdb-server/remove-remote REMOTE": removes REMOTE frmo the set of remotes
+ * that ovsdb-server services. */
+static void
+ovsdb_server_remove_remote(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                           const char *argv[], void *aux_)
+{
+    struct remove_remote_aux *aux = aux_;
+    struct sset_node *node;
+
+    node = sset_find(aux->remotes, argv[1]);
+    if (node) {
+        sset_delete(aux->remotes, node);
+        save_config(aux->config_tmpfile, aux->remotes);
+        unixctl_command_reply(conn, NULL);
+    } else {
+        unixctl_command_reply_error(conn, "no such remote");
+    }
+}
+
+/* "ovsdb-server/list-remotes": outputs a list of configured rmeotes. */
+static void
+ovsdb_server_list_remotes(struct unixctl_conn *conn, int argc OVS_UNUSED,
+                          const char *argv[] OVS_UNUSED, void *remotes_)
+{
+    struct sset *remotes = remotes_;
+    const char **list, **p;
+    struct ds s;
+
+    ds_init(&s);
+
+    list = sset_sort(remotes);
+    for (p = list; *p; p++) {
+        ds_put_format(&s, "%s\n", *p);
+    }
+    free(list);
+
+    unixctl_command_reply(conn, ds_cstr(&s));
+    ds_destroy(&s);
+}
+
 static void
 parse_options(int *argcp, char **argvp[],
               struct sset *remotes, char **unixctl_pathp, char **run_command)
@@ -846,10 +1012,9 @@ parse_options(int *argcp, char **argvp[],
         OPT_BOOTSTRAP_CA_CERT,
         OPT_ENABLE_DUMMY,
         VLOG_OPTION_ENUMS,
-        LEAK_CHECKER_OPTION_ENUMS,
         DAEMON_OPTION_ENUMS
     };
-    static struct option long_options[] = {
+    static const struct option long_options[] = {
         {"remote",      required_argument, NULL, OPT_REMOTE},
         {"unixctl",     required_argument, NULL, OPT_UNIXCTL},
         {"run",         required_argument, NULL, OPT_RUN},
@@ -857,7 +1022,6 @@ parse_options(int *argcp, char **argvp[],
         {"version",     no_argument, NULL, 'V'},
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
-        LEAK_CHECKER_LONG_OPTIONS,
         {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
         {"private-key", required_argument, NULL, 'p'},
         {"certificate", required_argument, NULL, 'c'},
@@ -900,7 +1064,6 @@ parse_options(int *argcp, char **argvp[],
 
         VLOG_OPTION_HANDLERS
         DAEMON_OPTION_HANDLERS
-        LEAK_CHECKER_OPTION_HANDLERS
 
         case 'p':
             private_key_file = optarg;
@@ -955,6 +1118,58 @@ usage(void)
            "  --unixctl=SOCKET        override default control socket name\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
-    leak_checker_usage();
     exit(EXIT_SUCCESS);
 }
+\f
+/* Truncates and replaces the contents of 'config_file' by a representation
+ * of 'remotes'. */
+static void
+save_config(FILE *config_file, const struct sset *remotes)
+{
+    const char *remote;
+    struct json *json;
+    char *s;
+
+    if (ftruncate(fileno(config_file), 0) == -1) {
+        VLOG_FATAL("failed to truncate temporary file (%s)", strerror(errno));
+    }
+
+    json = json_array_create_empty();
+    SSET_FOR_EACH (remote, remotes) {
+        json_array_add(json, json_string_create(remote));
+    }
+    s = json_to_string(json, 0);
+    json_destroy(json);
+
+    if (fseek(config_file, 0, SEEK_SET) != 0
+        || fputs(s, config_file) == EOF
+        || fflush(config_file) == EOF) {
+        VLOG_FATAL("failed to write temporary file (%s)", strerror(errno));
+    }
+    free(s);
+}
+
+/* Clears and replaces 'remotes' by a configuration read from 'config_file',
+ * which must have been previously written by save_config(). */
+static void
+load_config(FILE *config_file, struct sset *remotes)
+{
+    struct json *json;
+    size_t i;
+
+    sset_clear(remotes);
+
+    if (fseek(config_file, 0, SEEK_SET) != 0) {
+        VLOG_FATAL("seek failed in temporary file (%s)", strerror(errno));
+    }
+    json = json_from_stream(config_file);
+    if (json->type == JSON_STRING) {
+        VLOG_FATAL("reading json failed (%s)", json_string(json));
+    }
+    ovs_assert(json->type == JSON_ARRAY);
+    for (i = 0; i < json->u.array.n; i++) {
+        const struct json *remote = json->u.array.elems[i];
+        sset_add(remotes, json_string(remote));
+    }
+    json_destroy(json);
+}