diff mbox series

[ovs-dev,v2,07/17] ovsdb-server: Database config isolation.

Message ID 20240109225142.1987981-8-i.maximets@ovn.org
State Accepted
Commit e76f8472090edb29a0b1b49ea292473a598a6095
Delegated to: Ilya Maximets
Headers show
Series ovsdb-server: Configuration via config-file. | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_Build_and_Test success github build: passed

Commit Message

Ilya Maximets Jan. 9, 2024, 10:49 p.m. UTC
Add a new structure 'db_config' that holds the user-provided
configuration of the database.  And attach this configuration
to each of the databases on the server.

Each database has a service model: standalone, clustered, relay
or active-backup.  Relays and A-B databases have a source, each
source has its own set of JSON-RPC session options.  A-B also
have an indicator of it being active or backup and an optional
list of tables to exclude from replication.

All of that should be stored per database in the temporary
configuration file that is used in order to restore the config
after the OVSDB crash.  For that, the save/load functions are
also updates.

This change is written in generic way assuming all the databases
can have different configuration including service model.
The only user-visible change here is a slight modification of
the ovsdb-server/sync-status appctl, since it now needs to
skip databases that are not active-backup and also should report
active-backup databases that are currently active, i.e. not
added to the replication module.

If the service model is not defined in the configuration, it
is assumed to be standalone or clustered, and determined from
the storage type while opening the database.  If the service
model is defined, but doesn't match the actual storage type
in the database file, ovsdb-server will fail to open the
database.  This should never happen with internally generated
config file, but may happen in the future with user-provided
configuration files.  In this case the service model is used
for verification purposes only, if administrator wants to
assert a particular model.

Since the database 'source' connections can't use 'role' or
'read-only' options, a new flag added to corresponding JSON
parsing functions to skip these fields.

Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
---
 ovsdb/jsonrpc-server.c |  31 +-
 ovsdb/jsonrpc-server.h |   5 +-
 ovsdb/ovsdb-server.c   | 775 +++++++++++++++++++++++++++++++----------
 ovsdb/replication.c    |   1 -
 tests/ovsdb-server.at  |   8 +-
 5 files changed, 623 insertions(+), 197 deletions(-)

Comments

Dumitru Ceara Jan. 12, 2024, 4:17 p.m. UTC | #1
On 1/9/24 23:49, Ilya Maximets wrote:
> Add a new structure 'db_config' that holds the user-provided
> configuration of the database.  And attach this configuration
> to each of the databases on the server.
> 
> Each database has a service model: standalone, clustered, relay
> or active-backup.  Relays and A-B databases have a source, each
> source has its own set of JSON-RPC session options.  A-B also
> have an indicator of it being active or backup and an optional
> list of tables to exclude from replication.
> 
> All of that should be stored per database in the temporary
> configuration file that is used in order to restore the config
> after the OVSDB crash.  For that, the save/load functions are
> also updates.
> 
> This change is written in generic way assuming all the databases
> can have different configuration including service model.
> The only user-visible change here is a slight modification of
> the ovsdb-server/sync-status appctl, since it now needs to
> skip databases that are not active-backup and also should report
> active-backup databases that are currently active, i.e. not
> added to the replication module.
> 
> If the service model is not defined in the configuration, it
> is assumed to be standalone or clustered, and determined from
> the storage type while opening the database.  If the service
> model is defined, but doesn't match the actual storage type
> in the database file, ovsdb-server will fail to open the
> database.  This should never happen with internally generated
> config file, but may happen in the future with user-provided
> configuration files.  In this case the service model is used
> for verification purposes only, if administrator wants to
> assert a particular model.
> 
> Since the database 'source' connections can't use 'role' or
> 'read-only' options, a new flag added to corresponding JSON
> parsing functions to skip these fields.
> 
> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
> ---
>  ovsdb/jsonrpc-server.c |  31 +-
>  ovsdb/jsonrpc-server.h |   5 +-
>  ovsdb/ovsdb-server.c   | 775 +++++++++++++++++++++++++++++++----------
>  ovsdb/replication.c    |   1 -
>  tests/ovsdb-server.at  |   8 +-
>  5 files changed, 623 insertions(+), 197 deletions(-)
> 
> diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
> index 299afbb1d..81e9e48a0 100644
> --- a/ovsdb/jsonrpc-server.c
> +++ b/ovsdb/jsonrpc-server.c
> @@ -231,7 +231,8 @@ ovsdb_jsonrpc_options_clone(const struct ovsdb_jsonrpc_options *options)
>  }
>  
>  struct json *
> -ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
> +ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options,
> +                              bool jsonrpc_session_only)
>  {
>      struct json *json = json_object_create();
>  
> @@ -239,9 +240,15 @@ ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
>                      json_integer_create(options->max_backoff));
>      json_object_put(json, "inactivity-probe",
>                      json_integer_create(options->probe_interval));
> +    json_object_put(json, "dscp", json_integer_create(options->dscp));
> +
> +    if (jsonrpc_session_only) {
> +        /* Caller is not interested in OVSDB-specific options. */
> +        return json;
> +    }
> +
>      json_object_put(json, "read-only",
>                      json_boolean_create(options->read_only));
> -    json_object_put(json, "dscp", json_integer_create(options->dscp));
>      if (options->role) {
>          json_object_put(json, "role", json_string_create(options->role));
>      }
> @@ -251,7 +258,8 @@ ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
>  
>  void
>  ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *options,
> -                                       const struct json *json)
> +                                       const struct json *json,
> +                                       bool jsonrpc_session_only)
>  {
>      const struct json *max_backoff, *probe_interval, *read_only, *dscp, *role;
>      struct ovsdb_parser parser;
> @@ -271,22 +279,29 @@ ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *options,
>          options->probe_interval = json_integer(probe_interval);
>      }
>  
> +    dscp = ovsdb_parser_member(&parser, "dscp", OP_INTEGER | OP_OPTIONAL);
> +    if (dscp) {
> +        options->dscp = json_integer(dscp);
> +    }
> +
> +    if (jsonrpc_session_only) {
> +        /* Caller is not interested to OVSDB-specific options. */
> +        goto exit;
> +    }
> +
>      read_only = ovsdb_parser_member(&parser, "read-only",
>                                      OP_BOOLEAN | OP_OPTIONAL);
>      if (read_only) {
>          options->read_only = json_boolean(read_only);
>      }
>  
> -    dscp = ovsdb_parser_member(&parser, "dscp", OP_INTEGER | OP_OPTIONAL);
> -    if (dscp) {
> -        options->dscp = json_integer(dscp);
> -    }
> -
>      role = ovsdb_parser_member(&parser, "role", OP_STRING | OP_OPTIONAL);
>      if (role) {
> +        free(options->role);
>          options->role = nullable_xstrdup(json_string(role));
>      }
>  
> +exit:
>      error = ovsdb_parser_finish(&parser);
>      if (error) {
>          char *s = ovsdb_error_to_string_free(error);
> diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
> index 39366ad70..0d8f87da7 100644
> --- a/ovsdb/jsonrpc-server.h
> +++ b/ovsdb/jsonrpc-server.h
> @@ -45,10 +45,11 @@ struct ovsdb_jsonrpc_options *ovsdb_jsonrpc_options_clone(
>      const struct ovsdb_jsonrpc_options *);
>  
>  struct json *ovsdb_jsonrpc_options_to_json(
> -                                const struct ovsdb_jsonrpc_options *)
> +    const struct ovsdb_jsonrpc_options *, bool jsonrpc_session_only)
>      OVS_WARN_UNUSED_RESULT;
>  void ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *,
> -                                            const struct json *);
> +                                            const struct json *,
> +                                            bool jsonrpc_session_only);
>  
>  void ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *,
>                                        const struct shash *);
> diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
> index 9a3b0add1..d7a823220 100644
> --- a/ovsdb/ovsdb-server.c
> +++ b/ovsdb/ovsdb-server.c
> @@ -42,6 +42,7 @@
>  #include "ovsdb-data.h"
>  #include "ovsdb-types.h"
>  #include "ovsdb-error.h"
> +#include "ovsdb-parser.h"
>  #include "openvswitch/poll-loop.h"
>  #include "process.h"
>  #include "replication.h"
> @@ -65,12 +66,6 @@
>  
>  VLOG_DEFINE_THIS_MODULE(ovsdb_server);
>  
> -struct db {
> -    char *filename;
> -    struct ovsdb *db;
> -    struct uuid row_uuid;
> -};
> -
>  /* SSL configuration. */
>  static char *private_key_file;
>  static char *certificate_file;
> @@ -100,16 +95,79 @@ static unixctl_cb_func ovsdb_server_get_sync_exclude_tables;
>  static unixctl_cb_func ovsdb_server_get_sync_status;
>  static unixctl_cb_func ovsdb_server_get_db_storage_status;
>  
> +#define SERVICE_MODELS \
> +    SERVICE_MODEL(UNDEFINED,      undefined)         \
> +    SERVICE_MODEL(STANDALONE,     standalone)        \
> +    SERVICE_MODEL(CLUSTERED,      clustered)         \
> +    SERVICE_MODEL(ACTIVE_BACKUP,  active-backup)     \
> +    SERVICE_MODEL(RELAY,          relay)
> +
> +enum service_model {
> +#define SERVICE_MODEL(ENUM, NAME) SM_##ENUM,
> +    SERVICE_MODELS
> +#undef SERVICE_MODEL
> +};
> +
> +static const char *
> +service_model_to_string(enum service_model model)
> +{
> +    switch (model) {
> +#define SERVICE_MODEL(ENUM, NAME) \
> +    case SM_##ENUM: return #NAME;
> +    SERVICE_MODELS
> +#undef SERVICE_MODEL
> +    default: OVS_NOT_REACHED();
> +    }
> +}
> +
> +static enum service_model
> +service_model_from_string(const char *model)
> +{
> +#define SERVICE_MODEL(ENUM, NAME) \
> +    if (!strcmp(model, #NAME)) {  \
> +        return SM_##ENUM;         \
> +    }
> +    SERVICE_MODELS
> +#undef SERVICE_MODEL
> +
> +    VLOG_WARN("Unrecognized database service model: '%s'", model);
> +
> +    return SM_UNDEFINED;
> +}
> +
> +struct db_config {
> +    enum service_model model;
> +    char *source;  /* sync-from for backup or relay source. */
> +    struct ovsdb_jsonrpc_options *options;  /* For 'source' connection. */
> +
> +    /* Configuration specific to SM_ACTIVE_BACKUP. */
> +    struct {
> +        char *sync_exclude;  /* Tables to exclude. */
> +        bool backup;  /* If true, the database is read-only and receives
> +                       * updates from the 'source'. */
> +    } ab;

Nit: I mean, I get what 'ab' stands for, but I still find it an extra
step I need to do when reading the code.  I would've just defined the
fields in the outer structure I think.  But it's up to you. :)

> +};
> +
> +struct db {
> +    struct ovsdb *db;
> +    char *filename;
> +    struct db_config *config;
> +    struct uuid row_uuid;
> +};
> +
>  struct server_config {
>      struct shash *remotes;
> -    struct shash *all_dbs;
> -    FILE *config_tmpfile;
> +    struct shash *all_dbs;     /* All the currently serviced databases.
> +                                * 'struct db' by a schema name. */
> +    struct ovsdb_jsonrpc_server *jsonrpc;
> +
> +    /* Command line + appctl configuration. */
>      char **sync_from;
>      char **sync_exclude;
>      bool *is_backup;
>      int *replication_probe_interval;
>      int *relay_source_probe_interval;
> -    struct ovsdb_jsonrpc_server *jsonrpc;
> +    FILE *config_tmpfile;
>  };
>  static unixctl_cb_func ovsdb_server_add_remote;
>  static unixctl_cb_func ovsdb_server_remove_remote;
> @@ -123,14 +181,15 @@ static unixctl_cb_func ovsdb_server_tlog_list;
>  
>  static void read_db(struct server_config *, struct db *);
>  static struct ovsdb_error *open_db(struct server_config *,
> -                                   const char *filename)
> +                                   const char *filename,
> +                                   const struct db_config *)
>      OVS_WARN_UNUSED_RESULT;
>  static void add_server_db(struct server_config *);
>  static void remove_db(struct server_config *, struct shash_node *db, char *);
>  static void close_db(struct server_config *, struct db *, char *);
>  
>  static void parse_options(int argc, char *argvp[],
> -                          struct sset *db_filenames, struct shash *remotes,
> +                          struct shash *db_conf, struct shash *remotes,
>                            char **unixctl_pathp, char **run_command,
>                            char **sync_from, char **sync_exclude,
>                            bool *is_backup);
> @@ -153,29 +212,14 @@ static void update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
>  static void update_server_status(struct shash *all_dbs);
>  
>  static void save_config__(FILE *config_file, const struct shash *remotes,
> -                          const struct sset *db_filenames,
> +                          const struct shash *db_conf,
>                            const char *sync_from, const char *sync_exclude,
>                            bool is_backup);
>  static void save_config(struct server_config *);
>  static void load_config(FILE *config_file, struct shash *remotes,
> -                        struct sset *db_filenames, char **sync_from,
> +                        struct shash *db_conf, char **sync_from,
>                          char **sync_exclude, bool *is_backup);
>  
> -static void
> -ovsdb_replication_init(const char *sync_from, const char *exclude,
> -                       struct shash *all_dbs, const struct uuid *server_uuid,
> -                       int probe_interval)
> -{
> -    struct shash_node *node;
> -    SHASH_FOR_EACH (node, all_dbs) {
> -        struct db *db = node->data;
> -        if (node->name[0] != '_' && db->db) {
> -            replication_set_db(db->db, sync_from, exclude,
> -                               server_uuid, probe_interval);
> -        }
> -    }
> -}
> -
>  static void
>  log_and_free_error(struct ovsdb_error *error)
>  {
> @@ -186,11 +230,52 @@ log_and_free_error(struct ovsdb_error *error)
>      }
>  }
>  
> +static void
> +ovsdb_server_replication_remove_db(struct db *db)
> +{
> +    replication_remove_db(db->db);
> +    db->config->ab.backup = false;
> +}
> +
> +static void
> +ovsdb_server_replication_run(struct server_config *config)
> +{
> +    struct shash_node *node;
> +    bool all_alive = true;
> +
> +    replication_run();
> +
> +    SHASH_FOR_EACH (node, config->all_dbs) {
> +        struct db *db = node->data;
> +
> +        if (db->config->model == SM_ACTIVE_BACKUP && db->config->ab.backup
> +            && !replication_is_alive(db->db)) {
> +            ovsdb_server_replication_remove_db(db);
> +            all_alive = false;
> +        }
> +    }
> +
> +    /* If one connection is broken, switch all databases to active,
> +     * since they are configured via the same command line / appctl. */
> +    if (!all_alive && *config->is_backup) {
> +        *config->is_backup = false;
> +
> +        SHASH_FOR_EACH (node, config->all_dbs) {
> +            struct db *db = node->data;
> +
> +            if (db->config->model == SM_ACTIVE_BACKUP
> +                && db->config->ab.backup) {
> +                ovsdb_server_replication_remove_db(db);
> +            }
> +        }
> +    }
> +}
> +
>  static void
>  main_loop(struct server_config *config,
>            struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,
>            struct unixctl_server *unixctl, struct shash *remotes,
> -          struct process *run_process, bool *exiting, bool *is_backup)
> +          struct process *run_process, bool *exiting)
>  {
>      char *remotes_error, *ssl_error;
>      struct shash_node *node;
> @@ -220,7 +305,7 @@ main_loop(struct server_config *config,
>           * the set of remotes that reconfigure_remotes() uses. */
>          unixctl_server_run(unixctl);
>  
> -        ovsdb_jsonrpc_server_set_read_only(jsonrpc, *is_backup);
> +        ovsdb_jsonrpc_server_set_read_only(jsonrpc, false);
>  
>          report_error_if_changed(
>              reconfigure_remotes(jsonrpc, all_dbs, remotes),
> @@ -228,23 +313,7 @@ main_loop(struct server_config *config,
>          report_error_if_changed(reconfigure_ssl(all_dbs), &ssl_error);
>          ovsdb_jsonrpc_server_run(jsonrpc);
>  
> -        replication_run();
> -        if (*is_backup) {
> -            SHASH_FOR_EACH (node, all_dbs) {
> -                struct db *db = node->data;
> -                if (db->db->name[0] != '_' && !replication_is_alive(db->db)) {
> -                    *is_backup = false;
> -                    break;
> -                }
> -            }
> -            if (!*is_backup) {
> -                SHASH_FOR_EACH (node, all_dbs) {
> -                    struct db *db = node->data;
> -                    replication_remove_db(db->db);
> -                }
> -            }
> -        }
> -
> +        ovsdb_server_replication_run(config);
>          ovsdb_relay_run();
>  
>          SHASH_FOR_EACH_SAFE (node, all_dbs) {
> @@ -351,6 +420,92 @@ parse_relay_args(const char *arg, char **name, char **remote)
>      return true;
>  }
>  
> +static void
> +db_config_destroy(struct db_config *conf)
> +{
> +    if (!conf) {
> +        return;
> +    }
> +
> +    free(conf->source);
> +    if (conf->options) {
> +        free(conf->options->role);
> +        free(conf->options);
> +    }

I think I would've added a ovsdb_jsonrpc_options_free(struct
ovsdb_jsonrpc_options *); function.

> +    free(conf->ab.sync_exclude);
> +    free(conf);
> +}
> +
> +static struct db_config *
> +db_config_clone(const struct db_config *c)
> +{
> +    struct db_config *conf = xmemdup(c, sizeof *c);
> +
> +    conf->source = nullable_xstrdup(c->source);
> +    if (c->options) {
> +        conf->options = ovsdb_jsonrpc_options_clone(c->options);
> +    }
> +    conf->ab.sync_exclude = nullable_xstrdup(c->ab.sync_exclude);
> +
> +    return conf;
> +}
> +
> +static struct ovsdb_jsonrpc_options *
> +get_jsonrpc_options(const char *target, enum service_model model)
> +{
> +    struct ovsdb_jsonrpc_options *options;
> +
> +    options = ovsdb_jsonrpc_default_options(target);
> +    if (model == SM_ACTIVE_BACKUP) {
> +        options->probe_interval = REPLICATION_DEFAULT_PROBE_INTERVAL;
> +    } else if (model == SM_RELAY) {
> +        options->probe_interval = RELAY_SOURCE_DEFAULT_PROBE_INTERVAL;
> +    }
> +
> +    return options;
> +}
> +
> +static void
> +add_database_config(struct shash *db_conf, const char *opt,
> +                    const char *sync_from, const char *sync_exclude,
> +                    bool active)
> +{
> +    struct db_config *conf = xzalloc(sizeof *conf);
> +    char *filename = NULL;
> +
> +    if (parse_relay_args(opt, &filename, &conf->source)) {
> +        conf->model = SM_RELAY;
> +        conf->options = get_jsonrpc_options(conf->source, conf->model);
> +    } else if (sync_from) {
> +        conf->model = SM_ACTIVE_BACKUP;
> +        conf->source = xstrdup(sync_from);
> +        conf->options = get_jsonrpc_options(conf->source, conf->model);
> +        conf->ab.sync_exclude = nullable_xstrdup(sync_exclude);
> +        conf->ab.backup = !active;
> +        filename = xstrdup(opt);
> +    } else {
> +        conf->model = SM_UNDEFINED; /* We'll update once the file is open. */
> +        filename = xstrdup(opt);
> +    }
> +
> +    conf = shash_replace_nocopy(db_conf, filename, conf);
> +    if (conf) {
> +        VLOG_WARN("Duplicate database configuration: %s", filename);
> +        db_config_destroy(conf);
> +    }
> +}
> +
> +static void
> +free_database_configs(struct shash *db_conf)
> +{
> +    struct shash_node *node;
> +
> +    SHASH_FOR_EACH (node, db_conf) {
> +        db_config_destroy(node->data);
> +    }
> +    shash_clear(db_conf);
> +}
> +
>  int
>  main(int argc, char *argv[])
>  {
> @@ -358,11 +513,10 @@ main(int argc, char *argv[])
>      char *run_command = NULL;
>      struct unixctl_server *unixctl;
>      struct ovsdb_jsonrpc_server *jsonrpc;
> -    struct sset db_filenames;
> +    struct shash db_conf;
>      struct shash remotes;
>      char *sync_from, *sync_exclude;
>      bool is_backup;
> -    const char *db_filename;
>      struct process *run_process;
>      bool exiting;
>      int retval;
> @@ -381,7 +535,7 @@ main(int argc, char *argv[])
>      dns_resolve_init(true);
>  
>      bool active = false;
> -    parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path,
> +    parse_options(argc, argv, &db_conf, &remotes, &unixctl_path,
>                    &run_command, &sync_from, &sync_exclude, &active);
>      is_backup = sync_from && !active;
>  
> @@ -400,13 +554,15 @@ main(int argc, char *argv[])
>      server_config.remotes = &remotes;
>      server_config.config_tmpfile = config_tmpfile;
>  
> -    save_config__(config_tmpfile, &remotes, &db_filenames, sync_from,
> +    save_config__(config_tmpfile, &remotes, &db_conf, sync_from,
>                    sync_exclude, is_backup);
> +    free_remotes(&remotes);
> +    free_database_configs(&db_conf);
>  
>      daemonize_start(false, false);
>  
>      /* Load the saved config. */
> -    load_config(config_tmpfile, &remotes, &db_filenames, &sync_from,
> +    load_config(config_tmpfile, &remotes, &db_conf, &sync_from,
>                  &sync_exclude, &is_backup);
>  
>      /* Start ovsdb jsonrpc server. When running as a backup server,
> @@ -425,13 +581,16 @@ main(int argc, char *argv[])
>  
>      perf_counters_init();
>  
> -    SSET_FOR_EACH (db_filename, &db_filenames) {
> -        struct ovsdb_error *error = open_db(&server_config, db_filename);
> +    SHASH_FOR_EACH (node, &db_conf) {
> +        struct ovsdb_error *error = open_db(&server_config,
> +                                            node->name, node->data);
>          if (error) {
>              char *s = ovsdb_error_to_string_free(error);
>              ovs_fatal(0, "%s", s);
>          }
> +        db_config_destroy(node->data);
>      }
> +    shash_clear(&db_conf);
>      add_server_db(&server_config);
>  
>      char *error = reconfigure_remotes(jsonrpc, &all_dbs, &remotes);
> @@ -538,15 +697,8 @@ main(int argc, char *argv[])
>      unixctl_command_register("ovsdb-server/disable-monitor-cond", "", 0, 0,
>                               ovsdb_server_disable_monitor_cond, jsonrpc);
>  
> -    if (is_backup) {
> -        const struct uuid *server_uuid;
> -        server_uuid = ovsdb_jsonrpc_server_get_uuid(jsonrpc);
> -        ovsdb_replication_init(sync_from, sync_exclude, &all_dbs, server_uuid,
> -                               replication_probe_interval);
> -    }
> -
>      main_loop(&server_config, jsonrpc, &all_dbs, unixctl, &remotes,
> -              run_process, &exiting, &is_backup);
> +              run_process, &exiting);
>  
>      SHASH_FOR_EACH_SAFE (node, &all_dbs) {
>          struct db *db = node->data;
> @@ -556,7 +708,8 @@ main(int argc, char *argv[])
>      ovsdb_jsonrpc_server_destroy(jsonrpc);
>      shash_destroy(&all_dbs);
>      free_remotes(&remotes);
> -    sset_destroy(&db_filenames);
> +    free_database_configs(&db_conf);
> +    shash_destroy(&db_conf);
>      free(sync_from);
>      free(sync_exclude);
>      unixctl_server_destroy(unixctl);
> @@ -580,7 +733,7 @@ main(int argc, char *argv[])
>   *
>   * "False negatives" are possible. */
>  static bool
> -is_already_open(struct server_config *config OVS_UNUSED,
> +is_already_open(struct server_config *server_config OVS_UNUSED,
>                  const char *filename OVS_UNUSED)
>  {
>  #ifndef _WIN32
> @@ -589,11 +742,12 @@ is_already_open(struct server_config *config OVS_UNUSED,
>      if (!stat(filename, &s)) {
>          struct shash_node *node;
>  
> -        SHASH_FOR_EACH (node, config->all_dbs) {
> +        SHASH_FOR_EACH (node, server_config->all_dbs) {
>              struct db *db = node->data;
>              struct stat s2;
>  
> -            if (!stat(db->filename, &s2)
> +            if (db->config->model != SM_RELAY
> +                && !stat(db->filename, &s2)
>                  && s.st_dev == s2.st_dev
>                  && s.st_ino == s2.st_ino) {
>                  return true;
> @@ -606,16 +760,19 @@ is_already_open(struct server_config *config OVS_UNUSED,
>  }
>  
>  static void
> -close_db(struct server_config *config, struct db *db, char *comment)
> +close_db(struct server_config *server_config, struct db *db, char *comment)
>  {
>      if (db) {
> -        ovsdb_jsonrpc_server_remove_db(config->jsonrpc, db->db, comment);
> -        if (db->db->is_relay) {
> +        ovsdb_jsonrpc_server_remove_db(server_config->jsonrpc,
> +                                       db->db, comment);
> +        if (db->config->model == SM_RELAY) {
>              ovsdb_relay_del_db(db->db);
>          }
> -        if (*config->is_backup) {
> -            replication_remove_db(db->db);
> +        if (db->config->model == SM_ACTIVE_BACKUP
> +            && db->config->ab.backup) {
> +            ovsdb_server_replication_remove_db(db);
>          }
> +        db_config_destroy(db->config);
>          ovsdb_destroy(db->db);
>          free(db->filename);
>          free(db);
> @@ -768,20 +925,17 @@ add_db(struct server_config *config, struct db *db)
>  }
>  
>  static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
> -open_db(struct server_config *config, const char *filename)
> +open_db(struct server_config *server_config,
> +        const char *filename, const struct db_config *conf)
>  {
>      struct ovsdb_storage *storage;
> -    char *relay_remotes = NULL;
>      struct ovsdb_error *error;
> -    bool is_relay;
> -    char *name;
>  
> -    is_relay = parse_relay_args(filename, &name, &relay_remotes);
> -    if (!is_relay) {
> +    if (conf->model != SM_RELAY) {
>          /* If we know that the file is already open, return a good error
>           * message.  Otherwise, if the file is open, we'll fail later on with
>           * a harder to interpret file locking error. */
> -        if (is_already_open(config, filename)) {
> +        if (is_already_open(server_config, filename)) {
>              return ovsdb_error(NULL, "%s: already open", filename);
>          }
>  
> @@ -789,59 +943,78 @@ open_db(struct server_config *config, const char *filename)
>          if (error) {
>              return error;
>          }
> -        name = xstrdup(filename);
>      } else {
> -        storage = ovsdb_storage_create_unbacked(name);
> +        storage = ovsdb_storage_create_unbacked(filename);
> +    }
> +
> +    enum service_model model = conf->model;
> +    if (model == SM_UNDEFINED || model == SM_STANDALONE
> +        || model == SM_CLUSTERED) {
> +        /* Check the actual service model from the storage. */
> +        model = ovsdb_storage_is_clustered(storage)
> +                ? SM_CLUSTERED : SM_STANDALONE;
> +    }
> +    if (conf->model != SM_UNDEFINED && conf->model != model) {
> +        ovsdb_storage_close(storage);
> +        return ovsdb_error(NULL, "%s: database is %s and not %s",
> +                           filename, service_model_to_string(model),
> +                           service_model_to_string(conf->model));
>      }
>  
>      struct ovsdb_schema *schema;
> -    if (is_relay || ovsdb_storage_is_clustered(storage)) {
> +    if (model == SM_RELAY || model == SM_CLUSTERED) {
>          schema = NULL;
>      } else {
>          struct json *txn_json;
>          error = ovsdb_storage_read(storage, &schema, &txn_json, NULL);
>          if (error) {
>              ovsdb_storage_close(storage);
> -            free(name);
>              return error;
>          }
>          ovs_assert(schema && !txn_json);
>      }
>  
>      struct db *db = xzalloc(sizeof *db);
> -    db->filename = name;
> +    db->filename = xstrdup(filename);
> +    db->config = db_config_clone(conf);
> +    db->config->model = model;
>      db->db = ovsdb_create(schema, storage);
> -    ovsdb_jsonrpc_server_add_db(config->jsonrpc, db->db);
> +    ovsdb_jsonrpc_server_add_db(server_config->jsonrpc, db->db);
>  
>      /* Enable txn history for clustered and relay modes.  It is not enabled for
>       * other modes for now, since txn id is available for clustered and relay
>       * modes only. */
> -    ovsdb_txn_history_init(db->db,
> -                           is_relay || ovsdb_storage_is_clustered(storage));
> +    ovsdb_txn_history_init(db->db, model == SM_RELAY || model == SM_CLUSTERED);
>  
> -    read_db(config, db);
> +    read_db(server_config, db);
>  
>      error = (db->db->name[0] == '_'
>               ? ovsdb_error(NULL, "%s: names beginning with \"_\" are reserved",
>                             db->db->name)
> -             : shash_find(config->all_dbs, db->db->name)
> +             : shash_find(server_config->all_dbs, db->db->name)
>               ? ovsdb_error(NULL, "%s: duplicate database name", db->db->name)
>               : NULL);
>      if (error) {
>          char *error_s = ovsdb_error_to_string(error);
> -        close_db(config, db,
> +        close_db(server_config, db,
>                   xasprintf("cannot complete opening %s database (%s)",
>                             db->db->name, error_s));
>          free(error_s);
>          return error;
>      }
>  
> -    add_db(config, db);
> +    add_db(server_config, db);
> +
> +    if (model == SM_RELAY) {
> +        ovsdb_relay_add_db(db->db, conf->source, update_schema, server_config,
> +                           conf->options->probe_interval);
> +    }
> +    if (model == SM_ACTIVE_BACKUP && conf->ab.backup) {
> +        const struct uuid *server_uuid;
>  
> -    if (is_relay) {
> -        ovsdb_relay_add_db(db->db, relay_remotes, update_schema, config,
> -                           *config->relay_source_probe_interval);
> -        free(relay_remotes);
> +        server_uuid = ovsdb_jsonrpc_server_get_uuid(server_config->jsonrpc);
> +        replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
> +                           server_uuid, conf->options->probe_interval);
>      }
>      return NULL;
>  }
> @@ -865,6 +1038,8 @@ add_server_db(struct server_config *config)
>      /* We don't need txn_history for server_db. */
>  
>      db->filename = xstrdup("<internal>");
> +    db->config = xzalloc(sizeof *db->config);
> +    db->config->model = SM_UNDEFINED;
>      db->db = ovsdb_create(schema, ovsdb_storage_create_unbacked(NULL));
>      db->db->read_only = true;
>  
> @@ -1457,11 +1632,20 @@ ovsdb_server_set_active_ovsdb_server(struct unixctl_conn *conn,
>                                       void *config_)
>  {
>      struct server_config *config = config_;
> +    struct shash_node *node;
>  
> -    if (*config->sync_from) {
> -        free(*config->sync_from);
> -    }
> +    free(*config->sync_from);
>      *config->sync_from = xstrdup(argv[1]);
> +
> +    SHASH_FOR_EACH (node, config->all_dbs) {
> +        struct db *db = node->data;
> +
> +        if (db->config->model == SM_ACTIVE_BACKUP) {
> +            free(db->config->source);
> +            db->config->source = xstrdup(argv[1]);
> +        }
> +    }
> +
>      save_config(config);
>  
>      unixctl_command_reply(conn, NULL);
> @@ -1485,20 +1669,39 @@ ovsdb_server_connect_active_ovsdb_server(struct unixctl_conn *conn,
>                                           void *config_)
>  {
>      struct server_config *config = config_;
> +    struct shash_node *node;
>      char *msg = NULL;
>  
> -    if ( !*config->sync_from) {
> +    if (!*config->sync_from) {
>          msg = "Unable to connect: active server is not specified.\n";
>      } else {
>          const struct uuid *server_uuid;
>          server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> -        ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
> -                               config->all_dbs, server_uuid,
> -                               *config->replication_probe_interval);
> -        if (!*config->is_backup) {
> -            *config->is_backup = true;
> -            save_config(config);
> +
> +        SHASH_FOR_EACH (node, config->all_dbs) {
> +            struct db *db = node->data;
> +            struct db_config *conf = db->config;
> +
> +            /* This command also converts standalone databases to AB. */
> +            if (conf->model == SM_STANDALONE) {
> +                conf->model = SM_ACTIVE_BACKUP;
> +                conf->source = xstrdup(*config->sync_from);
> +                conf->options = ovsdb_jsonrpc_default_options(conf->source);
> +                conf->options->probe_interval =
> +                    *config->replication_probe_interval;
> +                conf->ab.sync_exclude =
> +                    nullable_xstrdup(*config->sync_exclude);
> +                conf->ab.backup = false;
> +            }
> +
> +            if (conf->model == SM_ACTIVE_BACKUP && !conf->ab.backup) {
> +                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
> +                                   server_uuid, conf->options->probe_interval);
> +                conf->ab.backup = true;
> +            }
>          }
> +        *config->is_backup = true;
> +        save_config(config);
>      }
>      unixctl_command_reply(conn, msg);
>  }
> @@ -1514,7 +1717,11 @@ ovsdb_server_disconnect_active_ovsdb_server(struct unixctl_conn *conn,
>  
>      SHASH_FOR_EACH (node, config->all_dbs) {
>          struct db *db = node->data;
> -        replication_remove_db(db->db);
> +        struct db_config *conf = db->config;
> +
> +        if (conf->model == SM_ACTIVE_BACKUP && conf->ab.backup) {
> +            ovsdb_server_replication_remove_db(db);
> +        }
>      }
>      *config->is_backup = false;
>      save_config(config);
> @@ -1528,23 +1735,35 @@ ovsdb_server_set_active_ovsdb_server_probe_interval(struct unixctl_conn *conn,
>                                                     void *config_)
>  {
>      struct server_config *config = config_;
> -
> +    struct shash_node *node;
>      int probe_interval;
> -    if (str_to_int(argv[1], 10, &probe_interval)) {
> -        *config->replication_probe_interval = probe_interval;
> -        save_config(config);
> -        if (*config->is_backup) {
> -            const struct uuid *server_uuid;
> -            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> -            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
> -                                   config->all_dbs, server_uuid,
> -                                   *config->replication_probe_interval);
> -        }
> -        unixctl_command_reply(conn, NULL);
> -    } else {
> -        unixctl_command_reply(
> +
> +    if (!str_to_int(argv[1], 10, &probe_interval)) {
> +        unixctl_command_reply_error(
>              conn, "Invalid probe interval, integer value expected");
> +        return;
> +    }
> +
> +    const struct uuid *server_uuid;
> +    server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> +
> +    *config->replication_probe_interval = probe_interval;
> +
> +    SHASH_FOR_EACH (node, config->all_dbs) {
> +        struct db *db = node->data;
> +        struct db_config *conf = db->config;
> +
> +        if (conf->model == SM_ACTIVE_BACKUP) {
> +            conf->options->probe_interval = probe_interval;
> +            if (conf->ab.backup) {
> +                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
> +                                   server_uuid, conf->options->probe_interval);
> +            }
> +        }
>      }
> +
> +    save_config(config);
> +    unixctl_command_reply(conn, NULL);
>  }
>  
>  static void
> @@ -1554,17 +1773,30 @@ ovsdb_server_set_relay_source_interval(struct unixctl_conn *conn,
>                                         void *config_)
>  {
>      struct server_config *config = config_;
> +    struct shash_node *node;
>      int probe_interval;
>  
> -    if (str_to_int(argv[1], 10, &probe_interval)) {
> -        *config->relay_source_probe_interval = probe_interval;
> -        save_config(config);
> -        ovsdb_relay_set_probe_interval(probe_interval);
> -        unixctl_command_reply(conn, NULL);
> -    } else {
> +    if (!str_to_int(argv[1], 10, &probe_interval)) {
>          unixctl_command_reply_error(
>              conn, "Invalid probe interval, integer value expected");
> +        return;
> +    }
> +
> +    *config->relay_source_probe_interval = probe_interval;
> +
> +    SHASH_FOR_EACH (node, config->all_dbs) {
> +        struct db *db = node->data;
> +        struct db_config *conf = db->config;
> +
> +        if (conf->model == SM_RELAY) {
> +            conf->options->probe_interval = probe_interval;
> +        }
>      }
> +
> +    ovsdb_relay_set_probe_interval(probe_interval);
> +    save_config(config);
> +
> +    unixctl_command_reply(conn, NULL);
>  }
>  
>  static void
> @@ -1574,20 +1806,36 @@ ovsdb_server_set_sync_exclude_tables(struct unixctl_conn *conn,
>                                       void *config_)
>  {
>      struct server_config *config = config_;
> +    struct shash_node *node;
>  
>      char *err = parse_excluded_tables(argv[1]);
> -    if (!err) {
> -        free(*config->sync_exclude);
> -        *config->sync_exclude = xstrdup(argv[1]);
> -        save_config(config);
> -        if (*config->is_backup) {
> -            const struct uuid *server_uuid;
> -            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> -            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
> -                                   config->all_dbs, server_uuid,
> -                                   *config->replication_probe_interval);
> +    if (err) {
> +        goto exit;
> +    }
> +
> +    const struct uuid *server_uuid;
> +    server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> +
> +    free(*config->sync_exclude);
> +    *config->sync_exclude = xstrdup(argv[1]);
> +
> +    SHASH_FOR_EACH (node, config->all_dbs) {
> +        struct db *db = node->data;
> +        struct db_config *conf = db->config;
> +
> +        if (conf->model == SM_ACTIVE_BACKUP) {
> +            free(conf->ab.sync_exclude);
> +            conf->ab.sync_exclude = xstrdup(argv[1]);
> +            if (conf->ab.backup) {
> +                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
> +                                   server_uuid, conf->options->probe_interval);
> +            }
>          }
>      }
> +
> +    save_config(config);
> +
> +exit:
>      unixctl_command_reply(conn, err);
>      free(err);
>  }
> @@ -1832,22 +2080,26 @@ ovsdb_server_add_database(struct unixctl_conn *conn, int argc OVS_UNUSED,
>  {
>      struct server_config *config = config_;
>      const char *filename = argv[1];
> +    const struct shash_node *node;
> +    struct shash db_conf;
> +
> +    shash_init(&db_conf);
> +    add_database_config(&db_conf, filename, *config->sync_from,
> +                        *config->sync_exclude, !config->is_backup);
> +    ovs_assert(shash_count(&db_conf) == 1);
> +    node = shash_first(&db_conf);
>  
> -    char *error = ovsdb_error_to_string_free(open_db(config, filename));
> +    char *error = ovsdb_error_to_string_free(open_db(config,
> +                                                     node->name, node->data));
>      if (!error) {
>          save_config(config);
> -        if (*config->is_backup) {
> -            const struct uuid *server_uuid;
> -            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
> -            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
> -                                   config->all_dbs, server_uuid,
> -                                   *config->replication_probe_interval);
> -        }
>          unixctl_command_reply(conn, NULL);
>      } else {
>          unixctl_command_reply_error(conn, error);
>          free(error);
>      }
> +    db_config_destroy(node->data);
> +    shash_destroy(&db_conf);
>  }
>  
>  static void
> @@ -1994,23 +2246,34 @@ ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED,
>                               const char *argv[] OVS_UNUSED, void *config_)
>  {
>      struct server_config *config = config_;
> -    bool is_backup = *config->is_backup;
>      struct ds ds = DS_EMPTY_INITIALIZER;
> +    bool any_backup = false;
>  
> -    ds_put_format(&ds, "state: %s\n", is_backup ? "backup" : "active");
> +    const struct shash_node **db_nodes = shash_sort(config->all_dbs);
>  
> -    if (is_backup) {
> -        const struct shash_node **db_nodes = shash_sort(config->all_dbs);
> +    for (size_t i = 0; i < shash_count(config->all_dbs); i++) {
> +        const struct db *db = db_nodes[i]->data;
>  
> -        for (size_t i = 0; i < shash_count(config->all_dbs); i++) {
> -            const struct db *db = db_nodes[i]->data;
> +        if (db->config->model != SM_ACTIVE_BACKUP) {
> +            continue;
> +        }
>  
> -            if (db->db && db->db->name[0] != '_') {
> -                ds_put_and_free_cstr(&ds, replication_status(db->db));
> -                ds_put_char(&ds, '\n');
> -            }
> +        any_backup = true;
> +
> +        ds_put_format(&ds, "database: %s\n", db->db->name);
> +        ds_put_format(&ds, "state: %s\n",
> +                      db->config->ab.backup ? "backup" : "active");
> +        if (db->config->ab.backup) {
> +            ds_put_and_free_cstr(&ds, replication_status(db->db));
>          }
> -        free(db_nodes);
> +        if (i + 1 < shash_count(config->all_dbs)) {
> +            ds_put_char(&ds, '\n');
> +        }
> +    }
> +    free(db_nodes);
> +
> +    if (!any_backup) {
> +        ds_put_cstr(&ds, "state: active\n");
>      }
>  
>      unixctl_command_reply(conn, ds_cstr(&ds));
> @@ -2054,7 +2317,7 @@ ovsdb_server_get_db_storage_status(struct unixctl_conn *conn,
>  
>  static void
>  parse_options(int argc, char *argv[],
> -              struct sset *db_filenames, struct shash *remotes,
> +              struct shash *db_conf, struct shash *remotes,
>                char **unixctl_pathp, char **run_command,
>                char **sync_from, char **sync_exclude, bool *active)
>  {
> @@ -2104,7 +2367,7 @@ parse_options(int argc, char *argv[],
>  
>      *sync_from = NULL;
>      *sync_exclude = NULL;
> -    sset_init(db_filenames);
> +    shash_init(db_conf);
>      shash_init(remotes);
>      for (;;) {
>          int c;
> @@ -2210,10 +2473,15 @@ parse_options(int argc, char *argv[],
>      argv += optind;
>      if (argc > 0) {
>          for (int i = 0; i < argc; i++) {
> -            sset_add(db_filenames, argv[i]);
> +            add_database_config(db_conf, argv[i], *sync_from, *sync_exclude,
> +                                *active);
>          }
>      } else if (add_default_db) {
> -        sset_add_and_free(db_filenames, xasprintf("%s/conf.db", ovs_dbdir()));
> +        char *filename = xasprintf("%s/conf.db", ovs_dbdir());
> +
> +        add_database_config(db_conf, filename, *sync_from, *sync_exclude,
> +                            *active);
> +        free(filename);
>      }
>  }
>  
> @@ -2264,16 +2532,63 @@ remotes_to_json(const struct shash *remotes)
>      json = json_object_create();
>      SHASH_FOR_EACH (node, remotes) {
>          json_object_put(json, node->name,
> -                        ovsdb_jsonrpc_options_to_json(node->data));
> +                        ovsdb_jsonrpc_options_to_json(node->data, false));
> +    }
> +    return json;
> +}
> +
> +static struct json *
> +db_config_to_json(const struct db_config *conf)
> +{
> +    struct json *json;
> +
> +    json = json_object_create();
> +
> +    if (conf->model != SM_UNDEFINED) {
> +        json_object_put(json, "service-model",
> +                        json_string_create(
> +                            service_model_to_string(conf->model)));
> +    }
> +
> +    if (conf->source) {
> +        struct json *source = json_object_create();
> +
> +        json_object_put(source, conf->source,
> +                        ovsdb_jsonrpc_options_to_json(conf->options, true));
> +        json_object_put(json, "source", source);
> +    }
> +
> +    if (conf->model == SM_ACTIVE_BACKUP) {
> +        if (conf->ab.sync_exclude) {
> +            struct sset set = SSET_INITIALIZER(&set);
> +
> +            sset_from_delimited_string(&set, conf->ab.sync_exclude, " ,");
> +            json_object_put(json, "exclude-tables", sset_to_json(&set));
> +            sset_destroy(&set);
> +        }
> +        json_object_put(json, "backup", json_boolean_create(conf->ab.backup));
> +    }
> +    return json;
> +}
> +
> +static struct json *
> +databases_to_json(const struct shash *db_conf)
> +{
> +    const struct shash_node *node;
> +    struct json *json;
> +
> +    json = json_object_create();
> +    SHASH_FOR_EACH (node, db_conf) {
> +        json_object_put(json, node->name, db_config_to_json(node->data));
>      }
>      return json;
>  }
>  
>  /* Truncates and replaces the contents of 'config_file' by a representation of
> - * 'remotes' and 'db_filenames'. */
> + * 'remotes', 'db_conf' and a few global replication paramaters. */
>  static void
>  save_config__(FILE *config_file, const struct shash *remotes,
> -              const struct sset *db_filenames, const char *sync_from,
> +              const struct shash *db_conf, const char *sync_from,
>                const char *sync_exclude, bool is_backup)
>  {
>      struct json *obj;
> @@ -2286,7 +2601,8 @@ save_config__(FILE *config_file, const struct shash *remotes,
>  
>      obj = json_object_create();
>      json_object_put(obj, "remotes", remotes_to_json(remotes));
> -    json_object_put(obj, "db_filenames", sset_to_json(db_filenames));
> +    json_object_put(obj, "databases", databases_to_json(db_conf));
> +
>      if (sync_from) {
>          json_object_put(obj, "sync_from", json_string_create(sync_from));
>      }
> @@ -2312,56 +2628,147 @@ save_config__(FILE *config_file, const struct shash *remotes,
>  static void
>  save_config(struct server_config *config)
>  {
> -    struct sset db_filenames;
>      struct shash_node *node;
> +    struct shash db_conf;
>  
> -    sset_init(&db_filenames);
> +    shash_init(&db_conf);
>      SHASH_FOR_EACH (node, config->all_dbs) {
>          struct db *db = node->data;
> +
>          if (node->name[0] != '_') {
> -            sset_add(&db_filenames, db->filename);
> +            shash_add(&db_conf, db->filename, db->config);
>          }
>      }
>  
> -    save_config__(config->config_tmpfile, config->remotes, &db_filenames,
> +    save_config__(config->config_tmpfile, config->remotes, &db_conf,
>                    *config->sync_from, *config->sync_exclude,
>                    *config->is_backup);
>  
> -    sset_destroy(&db_filenames);
> +    shash_destroy(&db_conf);
>  }
>  
>  static void
> -sset_from_json(struct sset *sset, const struct json *array)
> +remotes_from_json(struct shash *remotes, const struct json *json)
>  {
> -    size_t i;
> +    struct ovsdb_jsonrpc_options *options;
> +    const struct shash_node *node;
> +    const struct shash *object;
>  
> -    sset_clear(sset);
> +    free_remotes(remotes);
>  
> -    ovs_assert(array);
> -    ovs_assert(array->type == JSON_ARRAY);
> -    for (i = 0; i < array->array.n; i++) {
> -        const struct json *elem = array->array.elems[i];
> -        sset_add(sset, json_string(elem));
> +    ovs_assert(json);
> +    ovs_assert(json->type == JSON_OBJECT);
> +
> +    object = json_object(json);
> +    SHASH_FOR_EACH (node, object) {
> +        options = ovsdb_jsonrpc_default_options(node->name);
> +        ovsdb_jsonrpc_options_update_from_json(options, node->data, false);
> +        shash_add(remotes, node->name, options);
>      }
>  }
>  
> +static struct db_config *
> +db_config_from_json(const char *name, const struct json *json)
> +{
> +    const struct json *model, *source, *sync_exclude, *backup;
> +    struct db_config *conf = xzalloc(sizeof *conf);
> +    struct ovsdb_parser parser;
> +    struct ovsdb_error *error;
> +
> +    ovsdb_parser_init(&parser, json, "database %s", name);
> +
> +    model = ovsdb_parser_member(&parser, "service-model",
> +                                OP_STRING | OP_OPTIONAL);
> +    conf->model = model ? service_model_from_string(json_string(model))
> +                        : SM_UNDEFINED;
> +
> +    if (conf->model == SM_ACTIVE_BACKUP) {
> +        backup = ovsdb_parser_member(&parser, "backup", OP_BOOLEAN);
> +        conf->ab.backup = backup ? json_boolean(backup) : false;
> +
> +        sync_exclude = ovsdb_parser_member(&parser, "exclude-tables",
> +                                           OP_ARRAY | OP_OPTIONAL);
> +        if (sync_exclude) {
> +            const struct json_array *exclude = json_array(sync_exclude);
> +            struct sset set = SSET_INITIALIZER(&set);
> +
> +            for (size_t i = 0; i < exclude->n; i++) {
> +                if (exclude->elems[i]->type != JSON_STRING) {
> +                    ovsdb_parser_raise_error(&parser,
> +                        "'exclude-tables' must contain strings");
> +                    break;
> +                }
> +                sset_add(&set, json_string(exclude->elems[i]));
> +            }
> +            conf->ab.sync_exclude = sset_join(&set, ",", "");
> +            sset_destroy(&set);
> +        }
> +    }
> +
> +    if (conf->model == SM_ACTIVE_BACKUP || conf->model == SM_RELAY) {
> +        enum ovsdb_parser_types type = OP_OBJECT;
> +
> +        if (conf->model == SM_ACTIVE_BACKUP && !conf->ab.backup) {
> +            /* Active database doesn't have to have a source. */
> +            type |= OP_OPTIONAL;
> +        }
> +        source = ovsdb_parser_member(&parser, "source", type);
> +
> +        if (source && shash_count(json_object(source)) != 1) {
> +            ovsdb_parser_raise_error(&parser,
> +                "'source' should be an object with exactly one element");
> +        } else if (source) {
> +            const struct shash_node *node = shash_first(json_object(source));
> +            const struct json *options;
> +
> +            ovs_assert(node);
> +            conf->source = xstrdup(node->name);
> +            options = node->data;
> +
> +            conf->options = get_jsonrpc_options(conf->source, conf->model);
> +
> +            if (options->type == JSON_OBJECT) {
> +                ovsdb_jsonrpc_options_update_from_json(conf->options,
> +                                                       options, true);
> +            } else if (options->type != JSON_NULL) {
> +                ovsdb_parser_raise_error(&parser,
> +                    "JSON-RPC options is not a JSON object or null");
> +            }
> +        }
> +    }
> +
> +    error = ovsdb_parser_finish(&parser);
> +    if (error) {
> +        char *s = ovsdb_error_to_string_free(error);
> +
> +        VLOG_WARN("%s", s);
> +        free(s);
> +        db_config_destroy(conf);
> +        return NULL;
> +    }
> +
> +    return conf;
> +}
> +
> +
>  static void
> -remotes_from_json(struct shash *remotes, const struct json *json)
> +databases_from_json(struct shash *db_conf, const struct json *json)
>  {
> -    struct ovsdb_jsonrpc_options *options;
>      const struct shash_node *node;
>      const struct shash *object;
>  
> -    free_remotes(remotes);
> +    free_database_configs(db_conf);
>  
>      ovs_assert(json);
>      ovs_assert(json->type == JSON_OBJECT);
>  
>      object = json_object(json);
>      SHASH_FOR_EACH (node, object) {
> -        options = ovsdb_jsonrpc_default_options(node->name);
> -        ovsdb_jsonrpc_options_update_from_json(options, node->data);
> -        shash_add(remotes, node->name, options);
> +        struct db_config *conf = db_config_from_json(node->name, node->data);
> +
> +        if (conf) {
> +            shash_add(db_conf, node->name, conf);
> +        }
>      }
>  }
>  
> @@ -2369,7 +2776,7 @@ remotes_from_json(struct shash *remotes, const struct json *json)
>   * 'config_file', which must have been previously written by save_config(). */
>  static void
>  load_config(FILE *config_file, struct shash *remotes,
> -            struct sset *db_filenames, char **sync_from,
> +            struct shash *db_conf, char **sync_from,
>              char **sync_exclude, bool *is_backup)
>  {
>      struct json *json;
> @@ -2384,8 +2791,8 @@ load_config(FILE *config_file, struct shash *remotes,
>      ovs_assert(json->type == JSON_OBJECT);
>  
>      remotes_from_json(remotes, shash_find_data(json_object(json), "remotes"));
> -    sset_from_json(db_filenames,
> -                   shash_find_data(json_object(json), "db_filenames"));
> +    databases_from_json(db_conf,
> +                        shash_find_data(json_object(json), "databases"));
>  
>      struct json *string;
>      string = shash_find_data(json_object(json), "sync_from");
> diff --git a/ovsdb/replication.c b/ovsdb/replication.c
> index 3c59d4039..3a062b078 100644
> --- a/ovsdb/replication.c
> +++ b/ovsdb/replication.c
> @@ -795,7 +795,6 @@ replication_status(const struct ovsdb *db)
>      bool alive = rdb->session && jsonrpc_session_is_alive(rdb->session);
>      struct ds ds = DS_EMPTY_INITIALIZER;
>  
> -    ds_put_format(&ds, "database: %s\n", db->name);
>      if (alive) {
>          switch (rdb->state) {
>          case RPL_S_INIT:
> diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
> index 6eb758e22..45aa80cd6 100644
> --- a/tests/ovsdb-server.at
> +++ b/tests/ovsdb-server.at
> @@ -1988,7 +1988,9 @@ OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status |grep re
>  
>  dnl Switch the 'db1' to active
>  AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/disconnect-active-ovsdb-server])
> -AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status], [0], [state: active
> +AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status], [0], [dnl
> +database: mydb
> +state: active
>  ])
>  
>  dnl Issue a transaction to 'db1'
> @@ -2007,7 +2009,9 @@ AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/connect-active-ovsdb-server
>  
>  dnl Verify the change happend
>  OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status |grep replicating])
> -AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status], [0], [state: active
> +AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status], [0], [dnl
> +database: mydb
> +state: active
>  ])
>  
>  dnl Issue an transaction to 'db2' which is now active.

The rest looks neat, thanks!

Acked-by: Dumitru Ceara <dceara@redhat.com>
diff mbox series

Patch

diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
index 299afbb1d..81e9e48a0 100644
--- a/ovsdb/jsonrpc-server.c
+++ b/ovsdb/jsonrpc-server.c
@@ -231,7 +231,8 @@  ovsdb_jsonrpc_options_clone(const struct ovsdb_jsonrpc_options *options)
 }
 
 struct json *
-ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
+ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options,
+                              bool jsonrpc_session_only)
 {
     struct json *json = json_object_create();
 
@@ -239,9 +240,15 @@  ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
                     json_integer_create(options->max_backoff));
     json_object_put(json, "inactivity-probe",
                     json_integer_create(options->probe_interval));
+    json_object_put(json, "dscp", json_integer_create(options->dscp));
+
+    if (jsonrpc_session_only) {
+        /* Caller is not interested in OVSDB-specific options. */
+        return json;
+    }
+
     json_object_put(json, "read-only",
                     json_boolean_create(options->read_only));
-    json_object_put(json, "dscp", json_integer_create(options->dscp));
     if (options->role) {
         json_object_put(json, "role", json_string_create(options->role));
     }
@@ -251,7 +258,8 @@  ovsdb_jsonrpc_options_to_json(const struct ovsdb_jsonrpc_options *options)
 
 void
 ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *options,
-                                       const struct json *json)
+                                       const struct json *json,
+                                       bool jsonrpc_session_only)
 {
     const struct json *max_backoff, *probe_interval, *read_only, *dscp, *role;
     struct ovsdb_parser parser;
@@ -271,22 +279,29 @@  ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *options,
         options->probe_interval = json_integer(probe_interval);
     }
 
+    dscp = ovsdb_parser_member(&parser, "dscp", OP_INTEGER | OP_OPTIONAL);
+    if (dscp) {
+        options->dscp = json_integer(dscp);
+    }
+
+    if (jsonrpc_session_only) {
+        /* Caller is not interested to OVSDB-specific options. */
+        goto exit;
+    }
+
     read_only = ovsdb_parser_member(&parser, "read-only",
                                     OP_BOOLEAN | OP_OPTIONAL);
     if (read_only) {
         options->read_only = json_boolean(read_only);
     }
 
-    dscp = ovsdb_parser_member(&parser, "dscp", OP_INTEGER | OP_OPTIONAL);
-    if (dscp) {
-        options->dscp = json_integer(dscp);
-    }
-
     role = ovsdb_parser_member(&parser, "role", OP_STRING | OP_OPTIONAL);
     if (role) {
+        free(options->role);
         options->role = nullable_xstrdup(json_string(role));
     }
 
+exit:
     error = ovsdb_parser_finish(&parser);
     if (error) {
         char *s = ovsdb_error_to_string_free(error);
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
index 39366ad70..0d8f87da7 100644
--- a/ovsdb/jsonrpc-server.h
+++ b/ovsdb/jsonrpc-server.h
@@ -45,10 +45,11 @@  struct ovsdb_jsonrpc_options *ovsdb_jsonrpc_options_clone(
     const struct ovsdb_jsonrpc_options *);
 
 struct json *ovsdb_jsonrpc_options_to_json(
-                                const struct ovsdb_jsonrpc_options *)
+    const struct ovsdb_jsonrpc_options *, bool jsonrpc_session_only)
     OVS_WARN_UNUSED_RESULT;
 void ovsdb_jsonrpc_options_update_from_json(struct ovsdb_jsonrpc_options *,
-                                            const struct json *);
+                                            const struct json *,
+                                            bool jsonrpc_session_only);
 
 void ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *,
                                       const struct shash *);
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index 9a3b0add1..d7a823220 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -42,6 +42,7 @@ 
 #include "ovsdb-data.h"
 #include "ovsdb-types.h"
 #include "ovsdb-error.h"
+#include "ovsdb-parser.h"
 #include "openvswitch/poll-loop.h"
 #include "process.h"
 #include "replication.h"
@@ -65,12 +66,6 @@ 
 
 VLOG_DEFINE_THIS_MODULE(ovsdb_server);
 
-struct db {
-    char *filename;
-    struct ovsdb *db;
-    struct uuid row_uuid;
-};
-
 /* SSL configuration. */
 static char *private_key_file;
 static char *certificate_file;
@@ -100,16 +95,79 @@  static unixctl_cb_func ovsdb_server_get_sync_exclude_tables;
 static unixctl_cb_func ovsdb_server_get_sync_status;
 static unixctl_cb_func ovsdb_server_get_db_storage_status;
 
+#define SERVICE_MODELS \
+    SERVICE_MODEL(UNDEFINED,      undefined)         \
+    SERVICE_MODEL(STANDALONE,     standalone)        \
+    SERVICE_MODEL(CLUSTERED,      clustered)         \
+    SERVICE_MODEL(ACTIVE_BACKUP,  active-backup)     \
+    SERVICE_MODEL(RELAY,          relay)
+
+enum service_model {
+#define SERVICE_MODEL(ENUM, NAME) SM_##ENUM,
+    SERVICE_MODELS
+#undef SERVICE_MODEL
+};
+
+static const char *
+service_model_to_string(enum service_model model)
+{
+    switch (model) {
+#define SERVICE_MODEL(ENUM, NAME) \
+    case SM_##ENUM: return #NAME;
+    SERVICE_MODELS
+#undef SERVICE_MODEL
+    default: OVS_NOT_REACHED();
+    }
+}
+
+static enum service_model
+service_model_from_string(const char *model)
+{
+#define SERVICE_MODEL(ENUM, NAME) \
+    if (!strcmp(model, #NAME)) {  \
+        return SM_##ENUM;         \
+    }
+    SERVICE_MODELS
+#undef SERVICE_MODEL
+
+    VLOG_WARN("Unrecognized database service model: '%s'", model);
+
+    return SM_UNDEFINED;
+}
+
+struct db_config {
+    enum service_model model;
+    char *source;  /* sync-from for backup or relay source. */
+    struct ovsdb_jsonrpc_options *options;  /* For 'source' connection. */
+
+    /* Configuration specific to SM_ACTIVE_BACKUP. */
+    struct {
+        char *sync_exclude;  /* Tables to exclude. */
+        bool backup;  /* If true, the database is read-only and receives
+                       * updates from the 'source'. */
+    } ab;
+};
+
+struct db {
+    struct ovsdb *db;
+    char *filename;
+    struct db_config *config;
+    struct uuid row_uuid;
+};
+
 struct server_config {
     struct shash *remotes;
-    struct shash *all_dbs;
-    FILE *config_tmpfile;
+    struct shash *all_dbs;     /* All the currently serviced databases.
+                                * 'struct db' by a schema name. */
+    struct ovsdb_jsonrpc_server *jsonrpc;
+
+    /* Command line + appctl configuration. */
     char **sync_from;
     char **sync_exclude;
     bool *is_backup;
     int *replication_probe_interval;
     int *relay_source_probe_interval;
-    struct ovsdb_jsonrpc_server *jsonrpc;
+    FILE *config_tmpfile;
 };
 static unixctl_cb_func ovsdb_server_add_remote;
 static unixctl_cb_func ovsdb_server_remove_remote;
@@ -123,14 +181,15 @@  static unixctl_cb_func ovsdb_server_tlog_list;
 
 static void read_db(struct server_config *, struct db *);
 static struct ovsdb_error *open_db(struct server_config *,
-                                   const char *filename)
+                                   const char *filename,
+                                   const struct db_config *)
     OVS_WARN_UNUSED_RESULT;
 static void add_server_db(struct server_config *);
 static void remove_db(struct server_config *, struct shash_node *db, char *);
 static void close_db(struct server_config *, struct db *, char *);
 
 static void parse_options(int argc, char *argvp[],
-                          struct sset *db_filenames, struct shash *remotes,
+                          struct shash *db_conf, struct shash *remotes,
                           char **unixctl_pathp, char **run_command,
                           char **sync_from, char **sync_exclude,
                           bool *is_backup);
@@ -153,29 +212,14 @@  static void update_remote_status(const struct ovsdb_jsonrpc_server *jsonrpc,
 static void update_server_status(struct shash *all_dbs);
 
 static void save_config__(FILE *config_file, const struct shash *remotes,
-                          const struct sset *db_filenames,
+                          const struct shash *db_conf,
                           const char *sync_from, const char *sync_exclude,
                           bool is_backup);
 static void save_config(struct server_config *);
 static void load_config(FILE *config_file, struct shash *remotes,
-                        struct sset *db_filenames, char **sync_from,
+                        struct shash *db_conf, char **sync_from,
                         char **sync_exclude, bool *is_backup);
 
-static void
-ovsdb_replication_init(const char *sync_from, const char *exclude,
-                       struct shash *all_dbs, const struct uuid *server_uuid,
-                       int probe_interval)
-{
-    struct shash_node *node;
-    SHASH_FOR_EACH (node, all_dbs) {
-        struct db *db = node->data;
-        if (node->name[0] != '_' && db->db) {
-            replication_set_db(db->db, sync_from, exclude,
-                               server_uuid, probe_interval);
-        }
-    }
-}
-
 static void
 log_and_free_error(struct ovsdb_error *error)
 {
@@ -186,11 +230,52 @@  log_and_free_error(struct ovsdb_error *error)
     }
 }
 
+static void
+ovsdb_server_replication_remove_db(struct db *db)
+{
+    replication_remove_db(db->db);
+    db->config->ab.backup = false;
+}
+
+static void
+ovsdb_server_replication_run(struct server_config *config)
+{
+    struct shash_node *node;
+    bool all_alive = true;
+
+    replication_run();
+
+    SHASH_FOR_EACH (node, config->all_dbs) {
+        struct db *db = node->data;
+
+        if (db->config->model == SM_ACTIVE_BACKUP && db->config->ab.backup
+            && !replication_is_alive(db->db)) {
+            ovsdb_server_replication_remove_db(db);
+            all_alive = false;
+        }
+    }
+
+    /* If one connection is broken, switch all databases to active,
+     * since they are configured via the same command line / appctl. */
+    if (!all_alive && *config->is_backup) {
+        *config->is_backup = false;
+
+        SHASH_FOR_EACH (node, config->all_dbs) {
+            struct db *db = node->data;
+
+            if (db->config->model == SM_ACTIVE_BACKUP
+                && db->config->ab.backup) {
+                ovsdb_server_replication_remove_db(db);
+            }
+        }
+    }
+}
+
 static void
 main_loop(struct server_config *config,
           struct ovsdb_jsonrpc_server *jsonrpc, struct shash *all_dbs,
           struct unixctl_server *unixctl, struct shash *remotes,
-          struct process *run_process, bool *exiting, bool *is_backup)
+          struct process *run_process, bool *exiting)
 {
     char *remotes_error, *ssl_error;
     struct shash_node *node;
@@ -220,7 +305,7 @@  main_loop(struct server_config *config,
          * the set of remotes that reconfigure_remotes() uses. */
         unixctl_server_run(unixctl);
 
-        ovsdb_jsonrpc_server_set_read_only(jsonrpc, *is_backup);
+        ovsdb_jsonrpc_server_set_read_only(jsonrpc, false);
 
         report_error_if_changed(
             reconfigure_remotes(jsonrpc, all_dbs, remotes),
@@ -228,23 +313,7 @@  main_loop(struct server_config *config,
         report_error_if_changed(reconfigure_ssl(all_dbs), &ssl_error);
         ovsdb_jsonrpc_server_run(jsonrpc);
 
-        replication_run();
-        if (*is_backup) {
-            SHASH_FOR_EACH (node, all_dbs) {
-                struct db *db = node->data;
-                if (db->db->name[0] != '_' && !replication_is_alive(db->db)) {
-                    *is_backup = false;
-                    break;
-                }
-            }
-            if (!*is_backup) {
-                SHASH_FOR_EACH (node, all_dbs) {
-                    struct db *db = node->data;
-                    replication_remove_db(db->db);
-                }
-            }
-        }
-
+        ovsdb_server_replication_run(config);
         ovsdb_relay_run();
 
         SHASH_FOR_EACH_SAFE (node, all_dbs) {
@@ -351,6 +420,92 @@  parse_relay_args(const char *arg, char **name, char **remote)
     return true;
 }
 
+static void
+db_config_destroy(struct db_config *conf)
+{
+    if (!conf) {
+        return;
+    }
+
+    free(conf->source);
+    if (conf->options) {
+        free(conf->options->role);
+        free(conf->options);
+    }
+    free(conf->ab.sync_exclude);
+    free(conf);
+}
+
+static struct db_config *
+db_config_clone(const struct db_config *c)
+{
+    struct db_config *conf = xmemdup(c, sizeof *c);
+
+    conf->source = nullable_xstrdup(c->source);
+    if (c->options) {
+        conf->options = ovsdb_jsonrpc_options_clone(c->options);
+    }
+    conf->ab.sync_exclude = nullable_xstrdup(c->ab.sync_exclude);
+
+    return conf;
+}
+
+static struct ovsdb_jsonrpc_options *
+get_jsonrpc_options(const char *target, enum service_model model)
+{
+    struct ovsdb_jsonrpc_options *options;
+
+    options = ovsdb_jsonrpc_default_options(target);
+    if (model == SM_ACTIVE_BACKUP) {
+        options->probe_interval = REPLICATION_DEFAULT_PROBE_INTERVAL;
+    } else if (model == SM_RELAY) {
+        options->probe_interval = RELAY_SOURCE_DEFAULT_PROBE_INTERVAL;
+    }
+
+    return options;
+}
+
+static void
+add_database_config(struct shash *db_conf, const char *opt,
+                    const char *sync_from, const char *sync_exclude,
+                    bool active)
+{
+    struct db_config *conf = xzalloc(sizeof *conf);
+    char *filename = NULL;
+
+    if (parse_relay_args(opt, &filename, &conf->source)) {
+        conf->model = SM_RELAY;
+        conf->options = get_jsonrpc_options(conf->source, conf->model);
+    } else if (sync_from) {
+        conf->model = SM_ACTIVE_BACKUP;
+        conf->source = xstrdup(sync_from);
+        conf->options = get_jsonrpc_options(conf->source, conf->model);
+        conf->ab.sync_exclude = nullable_xstrdup(sync_exclude);
+        conf->ab.backup = !active;
+        filename = xstrdup(opt);
+    } else {
+        conf->model = SM_UNDEFINED; /* We'll update once the file is open. */
+        filename = xstrdup(opt);
+    }
+
+    conf = shash_replace_nocopy(db_conf, filename, conf);
+    if (conf) {
+        VLOG_WARN("Duplicate database configuration: %s", filename);
+        db_config_destroy(conf);
+    }
+}
+
+static void
+free_database_configs(struct shash *db_conf)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, db_conf) {
+        db_config_destroy(node->data);
+    }
+    shash_clear(db_conf);
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -358,11 +513,10 @@  main(int argc, char *argv[])
     char *run_command = NULL;
     struct unixctl_server *unixctl;
     struct ovsdb_jsonrpc_server *jsonrpc;
-    struct sset db_filenames;
+    struct shash db_conf;
     struct shash remotes;
     char *sync_from, *sync_exclude;
     bool is_backup;
-    const char *db_filename;
     struct process *run_process;
     bool exiting;
     int retval;
@@ -381,7 +535,7 @@  main(int argc, char *argv[])
     dns_resolve_init(true);
 
     bool active = false;
-    parse_options(argc, argv, &db_filenames, &remotes, &unixctl_path,
+    parse_options(argc, argv, &db_conf, &remotes, &unixctl_path,
                   &run_command, &sync_from, &sync_exclude, &active);
     is_backup = sync_from && !active;
 
@@ -400,13 +554,15 @@  main(int argc, char *argv[])
     server_config.remotes = &remotes;
     server_config.config_tmpfile = config_tmpfile;
 
-    save_config__(config_tmpfile, &remotes, &db_filenames, sync_from,
+    save_config__(config_tmpfile, &remotes, &db_conf, sync_from,
                   sync_exclude, is_backup);
+    free_remotes(&remotes);
+    free_database_configs(&db_conf);
 
     daemonize_start(false, false);
 
     /* Load the saved config. */
-    load_config(config_tmpfile, &remotes, &db_filenames, &sync_from,
+    load_config(config_tmpfile, &remotes, &db_conf, &sync_from,
                 &sync_exclude, &is_backup);
 
     /* Start ovsdb jsonrpc server. When running as a backup server,
@@ -425,13 +581,16 @@  main(int argc, char *argv[])
 
     perf_counters_init();
 
-    SSET_FOR_EACH (db_filename, &db_filenames) {
-        struct ovsdb_error *error = open_db(&server_config, db_filename);
+    SHASH_FOR_EACH (node, &db_conf) {
+        struct ovsdb_error *error = open_db(&server_config,
+                                            node->name, node->data);
         if (error) {
             char *s = ovsdb_error_to_string_free(error);
             ovs_fatal(0, "%s", s);
         }
+        db_config_destroy(node->data);
     }
+    shash_clear(&db_conf);
     add_server_db(&server_config);
 
     char *error = reconfigure_remotes(jsonrpc, &all_dbs, &remotes);
@@ -538,15 +697,8 @@  main(int argc, char *argv[])
     unixctl_command_register("ovsdb-server/disable-monitor-cond", "", 0, 0,
                              ovsdb_server_disable_monitor_cond, jsonrpc);
 
-    if (is_backup) {
-        const struct uuid *server_uuid;
-        server_uuid = ovsdb_jsonrpc_server_get_uuid(jsonrpc);
-        ovsdb_replication_init(sync_from, sync_exclude, &all_dbs, server_uuid,
-                               replication_probe_interval);
-    }
-
     main_loop(&server_config, jsonrpc, &all_dbs, unixctl, &remotes,
-              run_process, &exiting, &is_backup);
+              run_process, &exiting);
 
     SHASH_FOR_EACH_SAFE (node, &all_dbs) {
         struct db *db = node->data;
@@ -556,7 +708,8 @@  main(int argc, char *argv[])
     ovsdb_jsonrpc_server_destroy(jsonrpc);
     shash_destroy(&all_dbs);
     free_remotes(&remotes);
-    sset_destroy(&db_filenames);
+    free_database_configs(&db_conf);
+    shash_destroy(&db_conf);
     free(sync_from);
     free(sync_exclude);
     unixctl_server_destroy(unixctl);
@@ -580,7 +733,7 @@  main(int argc, char *argv[])
  *
  * "False negatives" are possible. */
 static bool
-is_already_open(struct server_config *config OVS_UNUSED,
+is_already_open(struct server_config *server_config OVS_UNUSED,
                 const char *filename OVS_UNUSED)
 {
 #ifndef _WIN32
@@ -589,11 +742,12 @@  is_already_open(struct server_config *config OVS_UNUSED,
     if (!stat(filename, &s)) {
         struct shash_node *node;
 
-        SHASH_FOR_EACH (node, config->all_dbs) {
+        SHASH_FOR_EACH (node, server_config->all_dbs) {
             struct db *db = node->data;
             struct stat s2;
 
-            if (!stat(db->filename, &s2)
+            if (db->config->model != SM_RELAY
+                && !stat(db->filename, &s2)
                 && s.st_dev == s2.st_dev
                 && s.st_ino == s2.st_ino) {
                 return true;
@@ -606,16 +760,19 @@  is_already_open(struct server_config *config OVS_UNUSED,
 }
 
 static void
-close_db(struct server_config *config, struct db *db, char *comment)
+close_db(struct server_config *server_config, struct db *db, char *comment)
 {
     if (db) {
-        ovsdb_jsonrpc_server_remove_db(config->jsonrpc, db->db, comment);
-        if (db->db->is_relay) {
+        ovsdb_jsonrpc_server_remove_db(server_config->jsonrpc,
+                                       db->db, comment);
+        if (db->config->model == SM_RELAY) {
             ovsdb_relay_del_db(db->db);
         }
-        if (*config->is_backup) {
-            replication_remove_db(db->db);
+        if (db->config->model == SM_ACTIVE_BACKUP
+            && db->config->ab.backup) {
+            ovsdb_server_replication_remove_db(db);
         }
+        db_config_destroy(db->config);
         ovsdb_destroy(db->db);
         free(db->filename);
         free(db);
@@ -768,20 +925,17 @@  add_db(struct server_config *config, struct db *db)
 }
 
 static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
-open_db(struct server_config *config, const char *filename)
+open_db(struct server_config *server_config,
+        const char *filename, const struct db_config *conf)
 {
     struct ovsdb_storage *storage;
-    char *relay_remotes = NULL;
     struct ovsdb_error *error;
-    bool is_relay;
-    char *name;
 
-    is_relay = parse_relay_args(filename, &name, &relay_remotes);
-    if (!is_relay) {
+    if (conf->model != SM_RELAY) {
         /* If we know that the file is already open, return a good error
          * message.  Otherwise, if the file is open, we'll fail later on with
          * a harder to interpret file locking error. */
-        if (is_already_open(config, filename)) {
+        if (is_already_open(server_config, filename)) {
             return ovsdb_error(NULL, "%s: already open", filename);
         }
 
@@ -789,59 +943,78 @@  open_db(struct server_config *config, const char *filename)
         if (error) {
             return error;
         }
-        name = xstrdup(filename);
     } else {
-        storage = ovsdb_storage_create_unbacked(name);
+        storage = ovsdb_storage_create_unbacked(filename);
+    }
+
+    enum service_model model = conf->model;
+    if (model == SM_UNDEFINED || model == SM_STANDALONE
+        || model == SM_CLUSTERED) {
+        /* Check the actual service model from the storage. */
+        model = ovsdb_storage_is_clustered(storage)
+                ? SM_CLUSTERED : SM_STANDALONE;
+    }
+    if (conf->model != SM_UNDEFINED && conf->model != model) {
+        ovsdb_storage_close(storage);
+        return ovsdb_error(NULL, "%s: database is %s and not %s",
+                           filename, service_model_to_string(model),
+                           service_model_to_string(conf->model));
     }
 
     struct ovsdb_schema *schema;
-    if (is_relay || ovsdb_storage_is_clustered(storage)) {
+    if (model == SM_RELAY || model == SM_CLUSTERED) {
         schema = NULL;
     } else {
         struct json *txn_json;
         error = ovsdb_storage_read(storage, &schema, &txn_json, NULL);
         if (error) {
             ovsdb_storage_close(storage);
-            free(name);
             return error;
         }
         ovs_assert(schema && !txn_json);
     }
 
     struct db *db = xzalloc(sizeof *db);
-    db->filename = name;
+    db->filename = xstrdup(filename);
+    db->config = db_config_clone(conf);
+    db->config->model = model;
     db->db = ovsdb_create(schema, storage);
-    ovsdb_jsonrpc_server_add_db(config->jsonrpc, db->db);
+    ovsdb_jsonrpc_server_add_db(server_config->jsonrpc, db->db);
 
     /* Enable txn history for clustered and relay modes.  It is not enabled for
      * other modes for now, since txn id is available for clustered and relay
      * modes only. */
-    ovsdb_txn_history_init(db->db,
-                           is_relay || ovsdb_storage_is_clustered(storage));
+    ovsdb_txn_history_init(db->db, model == SM_RELAY || model == SM_CLUSTERED);
 
-    read_db(config, db);
+    read_db(server_config, db);
 
     error = (db->db->name[0] == '_'
              ? ovsdb_error(NULL, "%s: names beginning with \"_\" are reserved",
                            db->db->name)
-             : shash_find(config->all_dbs, db->db->name)
+             : shash_find(server_config->all_dbs, db->db->name)
              ? ovsdb_error(NULL, "%s: duplicate database name", db->db->name)
              : NULL);
     if (error) {
         char *error_s = ovsdb_error_to_string(error);
-        close_db(config, db,
+        close_db(server_config, db,
                  xasprintf("cannot complete opening %s database (%s)",
                            db->db->name, error_s));
         free(error_s);
         return error;
     }
 
-    add_db(config, db);
+    add_db(server_config, db);
+
+    if (model == SM_RELAY) {
+        ovsdb_relay_add_db(db->db, conf->source, update_schema, server_config,
+                           conf->options->probe_interval);
+    }
+    if (model == SM_ACTIVE_BACKUP && conf->ab.backup) {
+        const struct uuid *server_uuid;
 
-    if (is_relay) {
-        ovsdb_relay_add_db(db->db, relay_remotes, update_schema, config,
-                           *config->relay_source_probe_interval);
-        free(relay_remotes);
+        server_uuid = ovsdb_jsonrpc_server_get_uuid(server_config->jsonrpc);
+        replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
+                           server_uuid, conf->options->probe_interval);
     }
     return NULL;
 }
@@ -865,6 +1038,8 @@  add_server_db(struct server_config *config)
     /* We don't need txn_history for server_db. */
 
     db->filename = xstrdup("<internal>");
+    db->config = xzalloc(sizeof *db->config);
+    db->config->model = SM_UNDEFINED;
     db->db = ovsdb_create(schema, ovsdb_storage_create_unbacked(NULL));
     db->db->read_only = true;
 
@@ -1457,11 +1632,20 @@  ovsdb_server_set_active_ovsdb_server(struct unixctl_conn *conn,
                                      void *config_)
 {
     struct server_config *config = config_;
+    struct shash_node *node;
 
-    if (*config->sync_from) {
-        free(*config->sync_from);
-    }
+    free(*config->sync_from);
     *config->sync_from = xstrdup(argv[1]);
+
+    SHASH_FOR_EACH (node, config->all_dbs) {
+        struct db *db = node->data;
+
+        if (db->config->model == SM_ACTIVE_BACKUP) {
+            free(db->config->source);
+            db->config->source = xstrdup(argv[1]);
+        }
+    }
+
     save_config(config);
 
     unixctl_command_reply(conn, NULL);
@@ -1485,20 +1669,39 @@  ovsdb_server_connect_active_ovsdb_server(struct unixctl_conn *conn,
                                          void *config_)
 {
     struct server_config *config = config_;
+    struct shash_node *node;
     char *msg = NULL;
 
-    if ( !*config->sync_from) {
+    if (!*config->sync_from) {
         msg = "Unable to connect: active server is not specified.\n";
     } else {
         const struct uuid *server_uuid;
         server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
-        ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
-                               config->all_dbs, server_uuid,
-                               *config->replication_probe_interval);
-        if (!*config->is_backup) {
-            *config->is_backup = true;
-            save_config(config);
+
+        SHASH_FOR_EACH (node, config->all_dbs) {
+            struct db *db = node->data;
+            struct db_config *conf = db->config;
+
+            /* This command also converts standalone databases to AB. */
+            if (conf->model == SM_STANDALONE) {
+                conf->model = SM_ACTIVE_BACKUP;
+                conf->source = xstrdup(*config->sync_from);
+                conf->options = ovsdb_jsonrpc_default_options(conf->source);
+                conf->options->probe_interval =
+                    *config->replication_probe_interval;
+                conf->ab.sync_exclude =
+                    nullable_xstrdup(*config->sync_exclude);
+                conf->ab.backup = false;
+            }
+
+            if (conf->model == SM_ACTIVE_BACKUP && !conf->ab.backup) {
+                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
+                                   server_uuid, conf->options->probe_interval);
+                conf->ab.backup = true;
+            }
         }
+        *config->is_backup = true;
+        save_config(config);
     }
     unixctl_command_reply(conn, msg);
 }
@@ -1514,7 +1717,11 @@  ovsdb_server_disconnect_active_ovsdb_server(struct unixctl_conn *conn,
 
     SHASH_FOR_EACH (node, config->all_dbs) {
         struct db *db = node->data;
-        replication_remove_db(db->db);
+        struct db_config *conf = db->config;
+
+        if (conf->model == SM_ACTIVE_BACKUP && conf->ab.backup) {
+            ovsdb_server_replication_remove_db(db);
+        }
     }
     *config->is_backup = false;
     save_config(config);
@@ -1528,23 +1735,35 @@  ovsdb_server_set_active_ovsdb_server_probe_interval(struct unixctl_conn *conn,
                                                    void *config_)
 {
     struct server_config *config = config_;
-
+    struct shash_node *node;
     int probe_interval;
-    if (str_to_int(argv[1], 10, &probe_interval)) {
-        *config->replication_probe_interval = probe_interval;
-        save_config(config);
-        if (*config->is_backup) {
-            const struct uuid *server_uuid;
-            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
-            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
-                                   config->all_dbs, server_uuid,
-                                   *config->replication_probe_interval);
-        }
-        unixctl_command_reply(conn, NULL);
-    } else {
-        unixctl_command_reply(
+
+    if (!str_to_int(argv[1], 10, &probe_interval)) {
+        unixctl_command_reply_error(
             conn, "Invalid probe interval, integer value expected");
+        return;
+    }
+
+    const struct uuid *server_uuid;
+    server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
+
+    *config->replication_probe_interval = probe_interval;
+
+    SHASH_FOR_EACH (node, config->all_dbs) {
+        struct db *db = node->data;
+        struct db_config *conf = db->config;
+
+        if (conf->model == SM_ACTIVE_BACKUP) {
+            conf->options->probe_interval = probe_interval;
+            if (conf->ab.backup) {
+                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
+                                   server_uuid, conf->options->probe_interval);
+            }
+        }
     }
+
+    save_config(config);
+    unixctl_command_reply(conn, NULL);
 }
 
 static void
@@ -1554,17 +1773,30 @@  ovsdb_server_set_relay_source_interval(struct unixctl_conn *conn,
                                        void *config_)
 {
     struct server_config *config = config_;
+    struct shash_node *node;
     int probe_interval;
 
-    if (str_to_int(argv[1], 10, &probe_interval)) {
-        *config->relay_source_probe_interval = probe_interval;
-        save_config(config);
-        ovsdb_relay_set_probe_interval(probe_interval);
-        unixctl_command_reply(conn, NULL);
-    } else {
+    if (!str_to_int(argv[1], 10, &probe_interval)) {
         unixctl_command_reply_error(
             conn, "Invalid probe interval, integer value expected");
+        return;
+    }
+
+    *config->relay_source_probe_interval = probe_interval;
+
+    SHASH_FOR_EACH (node, config->all_dbs) {
+        struct db *db = node->data;
+        struct db_config *conf = db->config;
+
+        if (conf->model == SM_RELAY) {
+            conf->options->probe_interval = probe_interval;
+        }
     }
+
+    ovsdb_relay_set_probe_interval(probe_interval);
+    save_config(config);
+
+    unixctl_command_reply(conn, NULL);
 }
 
 static void
@@ -1574,20 +1806,36 @@  ovsdb_server_set_sync_exclude_tables(struct unixctl_conn *conn,
                                      void *config_)
 {
     struct server_config *config = config_;
+    struct shash_node *node;
 
     char *err = parse_excluded_tables(argv[1]);
-    if (!err) {
-        free(*config->sync_exclude);
-        *config->sync_exclude = xstrdup(argv[1]);
-        save_config(config);
-        if (*config->is_backup) {
-            const struct uuid *server_uuid;
-            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
-            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
-                                   config->all_dbs, server_uuid,
-                                   *config->replication_probe_interval);
+    if (err) {
+        goto exit;
+    }
+
+    const struct uuid *server_uuid;
+    server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
+
+    free(*config->sync_exclude);
+    *config->sync_exclude = xstrdup(argv[1]);
+
+    SHASH_FOR_EACH (node, config->all_dbs) {
+        struct db *db = node->data;
+        struct db_config *conf = db->config;
+
+        if (conf->model == SM_ACTIVE_BACKUP) {
+            free(conf->ab.sync_exclude);
+            conf->ab.sync_exclude = xstrdup(argv[1]);
+            if (conf->ab.backup) {
+                replication_set_db(db->db, conf->source, conf->ab.sync_exclude,
+                                   server_uuid, conf->options->probe_interval);
+            }
         }
     }
+
+    save_config(config);
+
+exit:
     unixctl_command_reply(conn, err);
     free(err);
 }
@@ -1832,22 +2080,26 @@  ovsdb_server_add_database(struct unixctl_conn *conn, int argc OVS_UNUSED,
 {
     struct server_config *config = config_;
     const char *filename = argv[1];
+    const struct shash_node *node;
+    struct shash db_conf;
+
+    shash_init(&db_conf);
+    add_database_config(&db_conf, filename, *config->sync_from,
+                        *config->sync_exclude, !config->is_backup);
+    ovs_assert(shash_count(&db_conf) == 1);
+    node = shash_first(&db_conf);
 
-    char *error = ovsdb_error_to_string_free(open_db(config, filename));
+    char *error = ovsdb_error_to_string_free(open_db(config,
+                                                     node->name, node->data));
     if (!error) {
         save_config(config);
-        if (*config->is_backup) {
-            const struct uuid *server_uuid;
-            server_uuid = ovsdb_jsonrpc_server_get_uuid(config->jsonrpc);
-            ovsdb_replication_init(*config->sync_from, *config->sync_exclude,
-                                   config->all_dbs, server_uuid,
-                                   *config->replication_probe_interval);
-        }
         unixctl_command_reply(conn, NULL);
     } else {
         unixctl_command_reply_error(conn, error);
         free(error);
     }
+    db_config_destroy(node->data);
+    shash_destroy(&db_conf);
 }
 
 static void
@@ -1994,23 +2246,34 @@  ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED,
                              const char *argv[] OVS_UNUSED, void *config_)
 {
     struct server_config *config = config_;
-    bool is_backup = *config->is_backup;
     struct ds ds = DS_EMPTY_INITIALIZER;
+    bool any_backup = false;
 
-    ds_put_format(&ds, "state: %s\n", is_backup ? "backup" : "active");
+    const struct shash_node **db_nodes = shash_sort(config->all_dbs);
 
-    if (is_backup) {
-        const struct shash_node **db_nodes = shash_sort(config->all_dbs);
+    for (size_t i = 0; i < shash_count(config->all_dbs); i++) {
+        const struct db *db = db_nodes[i]->data;
 
-        for (size_t i = 0; i < shash_count(config->all_dbs); i++) {
-            const struct db *db = db_nodes[i]->data;
+        if (db->config->model != SM_ACTIVE_BACKUP) {
+            continue;
+        }
 
-            if (db->db && db->db->name[0] != '_') {
-                ds_put_and_free_cstr(&ds, replication_status(db->db));
-                ds_put_char(&ds, '\n');
-            }
+        any_backup = true;
+
+        ds_put_format(&ds, "database: %s\n", db->db->name);
+        ds_put_format(&ds, "state: %s\n",
+                      db->config->ab.backup ? "backup" : "active");
+        if (db->config->ab.backup) {
+            ds_put_and_free_cstr(&ds, replication_status(db->db));
         }
-        free(db_nodes);
+        if (i + 1 < shash_count(config->all_dbs)) {
+            ds_put_char(&ds, '\n');
+        }
+    }
+    free(db_nodes);
+
+    if (!any_backup) {
+        ds_put_cstr(&ds, "state: active\n");
     }
 
     unixctl_command_reply(conn, ds_cstr(&ds));
@@ -2054,7 +2317,7 @@  ovsdb_server_get_db_storage_status(struct unixctl_conn *conn,
 
 static void
 parse_options(int argc, char *argv[],
-              struct sset *db_filenames, struct shash *remotes,
+              struct shash *db_conf, struct shash *remotes,
               char **unixctl_pathp, char **run_command,
               char **sync_from, char **sync_exclude, bool *active)
 {
@@ -2104,7 +2367,7 @@  parse_options(int argc, char *argv[],
 
     *sync_from = NULL;
     *sync_exclude = NULL;
-    sset_init(db_filenames);
+    shash_init(db_conf);
     shash_init(remotes);
     for (;;) {
         int c;
@@ -2210,10 +2473,15 @@  parse_options(int argc, char *argv[],
     argv += optind;
     if (argc > 0) {
         for (int i = 0; i < argc; i++) {
-            sset_add(db_filenames, argv[i]);
+            add_database_config(db_conf, argv[i], *sync_from, *sync_exclude,
+                                *active);
         }
     } else if (add_default_db) {
-        sset_add_and_free(db_filenames, xasprintf("%s/conf.db", ovs_dbdir()));
+        char *filename = xasprintf("%s/conf.db", ovs_dbdir());
+
+        add_database_config(db_conf, filename, *sync_from, *sync_exclude,
+                            *active);
+        free(filename);
     }
 }
 
@@ -2264,16 +2532,63 @@  remotes_to_json(const struct shash *remotes)
     json = json_object_create();
     SHASH_FOR_EACH (node, remotes) {
         json_object_put(json, node->name,
-                        ovsdb_jsonrpc_options_to_json(node->data));
+                        ovsdb_jsonrpc_options_to_json(node->data, false));
+    }
+    return json;
+}
+
+static struct json *
+db_config_to_json(const struct db_config *conf)
+{
+    struct json *json;
+
+    json = json_object_create();
+
+    if (conf->model != SM_UNDEFINED) {
+        json_object_put(json, "service-model",
+                        json_string_create(
+                            service_model_to_string(conf->model)));
+    }
+
+    if (conf->source) {
+        struct json *source = json_object_create();
+
+        json_object_put(source, conf->source,
+                        ovsdb_jsonrpc_options_to_json(conf->options, true));
+        json_object_put(json, "source", source);
+    }
+
+    if (conf->model == SM_ACTIVE_BACKUP) {
+        if (conf->ab.sync_exclude) {
+            struct sset set = SSET_INITIALIZER(&set);
+
+            sset_from_delimited_string(&set, conf->ab.sync_exclude, " ,");
+            json_object_put(json, "exclude-tables", sset_to_json(&set));
+            sset_destroy(&set);
+        }
+        json_object_put(json, "backup", json_boolean_create(conf->ab.backup));
+    }
+    return json;
+}
+
+static struct json *
+databases_to_json(const struct shash *db_conf)
+{
+    const struct shash_node *node;
+    struct json *json;
+
+    json = json_object_create();
+    SHASH_FOR_EACH (node, db_conf) {
+        json_object_put(json, node->name, db_config_to_json(node->data));
     }
     return json;
 }
 
 /* Truncates and replaces the contents of 'config_file' by a representation of
- * 'remotes' and 'db_filenames'. */
+ * 'remotes', 'db_conf' and a few global replication paramaters. */
 static void
 save_config__(FILE *config_file, const struct shash *remotes,
-              const struct sset *db_filenames, const char *sync_from,
+              const struct shash *db_conf, const char *sync_from,
               const char *sync_exclude, bool is_backup)
 {
     struct json *obj;
@@ -2286,7 +2601,8 @@  save_config__(FILE *config_file, const struct shash *remotes,
 
     obj = json_object_create();
     json_object_put(obj, "remotes", remotes_to_json(remotes));
-    json_object_put(obj, "db_filenames", sset_to_json(db_filenames));
+    json_object_put(obj, "databases", databases_to_json(db_conf));
+
     if (sync_from) {
         json_object_put(obj, "sync_from", json_string_create(sync_from));
     }
@@ -2312,56 +2628,147 @@  save_config__(FILE *config_file, const struct shash *remotes,
 static void
 save_config(struct server_config *config)
 {
-    struct sset db_filenames;
     struct shash_node *node;
+    struct shash db_conf;
 
-    sset_init(&db_filenames);
+    shash_init(&db_conf);
     SHASH_FOR_EACH (node, config->all_dbs) {
         struct db *db = node->data;
+
         if (node->name[0] != '_') {
-            sset_add(&db_filenames, db->filename);
+            shash_add(&db_conf, db->filename, db->config);
         }
     }
 
-    save_config__(config->config_tmpfile, config->remotes, &db_filenames,
+    save_config__(config->config_tmpfile, config->remotes, &db_conf,
                   *config->sync_from, *config->sync_exclude,
                   *config->is_backup);
 
-    sset_destroy(&db_filenames);
+    shash_destroy(&db_conf);
 }
 
 static void
-sset_from_json(struct sset *sset, const struct json *array)
+remotes_from_json(struct shash *remotes, const struct json *json)
 {
-    size_t i;
+    struct ovsdb_jsonrpc_options *options;
+    const struct shash_node *node;
+    const struct shash *object;
 
-    sset_clear(sset);
+    free_remotes(remotes);
 
-    ovs_assert(array);
-    ovs_assert(array->type == JSON_ARRAY);
-    for (i = 0; i < array->array.n; i++) {
-        const struct json *elem = array->array.elems[i];
-        sset_add(sset, json_string(elem));
+    ovs_assert(json);
+    ovs_assert(json->type == JSON_OBJECT);
+
+    object = json_object(json);
+    SHASH_FOR_EACH (node, object) {
+        options = ovsdb_jsonrpc_default_options(node->name);
+        ovsdb_jsonrpc_options_update_from_json(options, node->data, false);
+        shash_add(remotes, node->name, options);
     }
 }
 
+static struct db_config *
+db_config_from_json(const char *name, const struct json *json)
+{
+    const struct json *model, *source, *sync_exclude, *backup;
+    struct db_config *conf = xzalloc(sizeof *conf);
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+
+    ovsdb_parser_init(&parser, json, "database %s", name);
+
+    model = ovsdb_parser_member(&parser, "service-model",
+                                OP_STRING | OP_OPTIONAL);
+    conf->model = model ? service_model_from_string(json_string(model))
+                        : SM_UNDEFINED;
+
+    if (conf->model == SM_ACTIVE_BACKUP) {
+        backup = ovsdb_parser_member(&parser, "backup", OP_BOOLEAN);
+        conf->ab.backup = backup ? json_boolean(backup) : false;
+
+        sync_exclude = ovsdb_parser_member(&parser, "exclude-tables",
+                                           OP_ARRAY | OP_OPTIONAL);
+        if (sync_exclude) {
+            const struct json_array *exclude = json_array(sync_exclude);
+            struct sset set = SSET_INITIALIZER(&set);
+
+            for (size_t i = 0; i < exclude->n; i++) {
+                if (exclude->elems[i]->type != JSON_STRING) {
+                    ovsdb_parser_raise_error(&parser,
+                        "'exclude-tables' must contain strings");
+                    break;
+                }
+                sset_add(&set, json_string(exclude->elems[i]));
+            }
+            conf->ab.sync_exclude = sset_join(&set, ",", "");
+            sset_destroy(&set);
+        }
+    }
+
+    if (conf->model == SM_ACTIVE_BACKUP || conf->model == SM_RELAY) {
+        enum ovsdb_parser_types type = OP_OBJECT;
+
+        if (conf->model == SM_ACTIVE_BACKUP && !conf->ab.backup) {
+            /* Active database doesn't have to have a source. */
+            type |= OP_OPTIONAL;
+        }
+        source = ovsdb_parser_member(&parser, "source", type);
+
+        if (source && shash_count(json_object(source)) != 1) {
+            ovsdb_parser_raise_error(&parser,
+                "'source' should be an object with exactly one element");
+        } else if (source) {
+            const struct shash_node *node = shash_first(json_object(source));
+            const struct json *options;
+
+            ovs_assert(node);
+            conf->source = xstrdup(node->name);
+            options = node->data;
+
+            conf->options = get_jsonrpc_options(conf->source, conf->model);
+
+            if (options->type == JSON_OBJECT) {
+                ovsdb_jsonrpc_options_update_from_json(conf->options,
+                                                       options, true);
+            } else if (options->type != JSON_NULL) {
+                ovsdb_parser_raise_error(&parser,
+                    "JSON-RPC options is not a JSON object or null");
+            }
+        }
+    }
+
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        char *s = ovsdb_error_to_string_free(error);
+
+        VLOG_WARN("%s", s);
+        free(s);
+        db_config_destroy(conf);
+        return NULL;
+    }
+
+    return conf;
+}
+
+
 static void
-remotes_from_json(struct shash *remotes, const struct json *json)
+databases_from_json(struct shash *db_conf, const struct json *json)
 {
-    struct ovsdb_jsonrpc_options *options;
     const struct shash_node *node;
     const struct shash *object;
 
-    free_remotes(remotes);
+    free_database_configs(db_conf);
 
     ovs_assert(json);
     ovs_assert(json->type == JSON_OBJECT);
 
     object = json_object(json);
     SHASH_FOR_EACH (node, object) {
-        options = ovsdb_jsonrpc_default_options(node->name);
-        ovsdb_jsonrpc_options_update_from_json(options, node->data);
-        shash_add(remotes, node->name, options);
+        struct db_config *conf = db_config_from_json(node->name, node->data);
+
+        if (conf) {
+            shash_add(db_conf, node->name, conf);
+        }
     }
 }
 
@@ -2369,7 +2776,7 @@  remotes_from_json(struct shash *remotes, const struct json *json)
  * 'config_file', which must have been previously written by save_config(). */
 static void
 load_config(FILE *config_file, struct shash *remotes,
-            struct sset *db_filenames, char **sync_from,
+            struct shash *db_conf, char **sync_from,
             char **sync_exclude, bool *is_backup)
 {
     struct json *json;
@@ -2384,8 +2791,8 @@  load_config(FILE *config_file, struct shash *remotes,
     ovs_assert(json->type == JSON_OBJECT);
 
     remotes_from_json(remotes, shash_find_data(json_object(json), "remotes"));
-    sset_from_json(db_filenames,
-                   shash_find_data(json_object(json), "db_filenames"));
+    databases_from_json(db_conf,
+                        shash_find_data(json_object(json), "databases"));
 
     struct json *string;
     string = shash_find_data(json_object(json), "sync_from");
diff --git a/ovsdb/replication.c b/ovsdb/replication.c
index 3c59d4039..3a062b078 100644
--- a/ovsdb/replication.c
+++ b/ovsdb/replication.c
@@ -795,7 +795,6 @@  replication_status(const struct ovsdb *db)
     bool alive = rdb->session && jsonrpc_session_is_alive(rdb->session);
     struct ds ds = DS_EMPTY_INITIALIZER;
 
-    ds_put_format(&ds, "database: %s\n", db->name);
     if (alive) {
         switch (rdb->state) {
         case RPL_S_INIT:
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index 6eb758e22..45aa80cd6 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -1988,7 +1988,9 @@  OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status |grep re
 
 dnl Switch the 'db1' to active
 AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/disconnect-active-ovsdb-server])
-AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status], [0], [state: active
+AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status], [0], [dnl
+database: mydb
+state: active
 ])
 
 dnl Issue a transaction to 'db1'
@@ -2007,7 +2009,9 @@  AT_CHECK([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/connect-active-ovsdb-server
 
 dnl Verify the change happend
 OVS_WAIT_UNTIL([ovs-appctl -t "`pwd`"/unixctl ovsdb-server/sync-status |grep replicating])
-AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status], [0], [state: active
+AT_CHECK([ovs-appctl -t "`pwd`"/unixctl2 ovsdb-server/sync-status], [0], [dnl
+database: mydb
+state: active
 ])
 
 dnl Issue an transaction to 'db2' which is now active.