/* * repmgr-action-standby.c * * Implements standby actions for the repmgr command line utility * * Copyright (c) 2ndQuadrant, 2010-2017 */ #include "repmgr.h" #include "dirutil.h" #include "repmgr-client-global.h" #include "repmgr-action-standby.h" static PGconn *primary_conn = NULL; static PGconn *source_conn = NULL; static char local_data_directory[MAXPGPATH]; static bool local_data_directory_provided = false; static bool upstream_record_found = false; static int upstream_node_id = UNKNOWN_NODE_ID; static char upstream_data_directory[MAXPGPATH]; static t_conninfo_param_list recovery_conninfo; static char recovery_conninfo_str[MAXLEN]; static standy_clone_mode mode; /* used by barman mode */ static char local_repmgr_tmp_directory[MAXPGPATH]; static void check_barman_config(void); static char *make_barman_ssh_command(char *buf); static void check_source_server(void); void do_standby_clone(void) { /* * conninfo params for the actual upstream node (which might be different * to the node we're cloning from) to write to recovery.conf */ /* * detecting the cloning mode */ mode = get_standby_clone_mode(); /* * In rsync mode, we need to check the SSH connection early */ if (mode == rsync) { int r; r = test_ssh_connection(runtime_options.host, runtime_options.remote_user); if (r != 0) { log_error(_("remote host %s is not reachable via SSH"), runtime_options.host); exit(ERR_BAD_SSH); } } /* * If dest_dir (-D/--pgdata) was provided, this will become the new data * directory (otherwise repmgr will default to using the same directory * path as on the source host). * * Note that barman mode requires -D/--pgdata. * * If -D/--pgdata is not supplied, and we're not cloning from barman, * the source host's data directory will be fetched later, after * we've connected to it. */ if (runtime_options.data_dir[0]) { local_data_directory_provided = true; log_notice(_("destination directory '%s' provided"), runtime_options.data_dir); } else if (mode == barman) { log_error(_("Barman mode requires a data directory")); log_hint(_("use -D/--pgdata to explicitly specify a data directory")); exit(ERR_BAD_CONFIG); } /* Sanity-check barman connection and installation */ if (mode == barman) { /* this will exit with ERR_BARMAN if problems found */ check_barman_config(); } /* * target directory (-D/--pgdata) provided - use that as new data directory * (useful when executing backup on local machine only or creating the backup * in a different local directory when backup source is a remote host) */ if (local_data_directory_provided == true) { strncpy(local_data_directory, runtime_options.data_dir, MAXPGPATH); } /* * Initialise list of conninfo parameters which will later be used * to create the `primary_conninfo` string in recovery.conf . * * We'll initialise it with the default values as seen by libpq, * and overwrite them with the host settings specified on the command * line. As it's possible the standby will be cloned from a node different * to its intended upstream, we'll later attempt to fetch the * upstream node record and overwrite the values set here with * those from the upstream node record (excluding that record's * application_name) */ initialize_conninfo_params(&recovery_conninfo, true); copy_conninfo_params(&recovery_conninfo, &source_conninfo); /* * If application_name is set in repmgr.conf's conninfo parameter, use * this value (if the source host was provided as a conninfo string, any * application_name values set there will be overridden; we assume the only * reason to pass an application_name via the command line is in the * rare corner case where a user wishes to clone a server without * providing repmgr.conf) */ if (strlen(config_file_options.conninfo)) { char application_name[MAXLEN] = ""; get_conninfo_value(config_file_options.conninfo, "application_name", application_name); if (strlen(application_name)) { param_set(&recovery_conninfo, "application_name", application_name); } } /* * --upstream-conninfo supplied, which we interpret to imply * --no-upstream-connection as well - the use case for this option is when * the upstream is not available, so no point in checking for it. */ if (*runtime_options.upstream_conninfo) runtime_options.no_upstream_connection = false; /* By default attempt to connect to the source server */ if (runtime_options.no_upstream_connection == false) { check_source_server(); } } void check_barman_config(void) { char datadir_list_filename[MAXLEN]; char barman_command_buf[MAXLEN] = ""; char command[MAXLEN]; bool command_ok; /* * Check that there is at least one valid backup */ log_info(_("connecting to Barman server to verify backup for %s"), config_file_options.barman_server); maxlen_snprintf(command, "%s show-backup %s latest > /dev/null", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server); command_ok = local_command(command, NULL); if (command_ok == false) { log_error(_("no valid backup for server %s was found in the Barman catalogue"), config_file_options.barman_server); log_hint(_("refer to the Barman documentation for more information\n")); exit(ERR_BARMAN); } /* * Create the local repmgr subdirectory */ maxlen_snprintf(local_repmgr_tmp_directory, "%s/repmgr", local_data_directory); maxlen_snprintf(datadir_list_filename, "%s/data.txt", local_repmgr_tmp_directory); if (!create_pg_dir(local_data_directory, runtime_options.force)) { log_error(_("unable to use directory %s"), local_data_directory); log_hint(_("use -F/--force option to force this directory to be overwritten\n")); exit(ERR_BAD_CONFIG); } if (!create_pg_dir(local_repmgr_tmp_directory, runtime_options.force)) { log_error(_("unable to create directory \"%s\""), local_repmgr_tmp_directory); exit(ERR_BAD_CONFIG); } /* * Fetch server parameters from Barman */ log_info(_("connecting to Barman server to fetch server parameters")); maxlen_snprintf(command, "%s show-server %s > %s/show-server.txt", make_barman_ssh_command(barman_command_buf), config_file_options.barman_server, local_repmgr_tmp_directory); command_ok = local_command(command, NULL); if (command_ok == false) { log_error(_("unable to fetch server parameters from Barman server")); exit(ERR_BARMAN); } } static char * make_barman_ssh_command(char *buf) { static char config_opt[MAXLEN] = ""; if (strlen(config_file_options.barman_config)) maxlen_snprintf(config_opt, " --config=%s", config_file_options.barman_config); maxlen_snprintf(buf, "ssh %s barman%s", config_file_options.barman_server, config_opt); return buf; } static void check_source_server() { int server_version_num = UNKNOWN_SERVER_VERSION_NUM; char cluster_size[MAXLEN]; /* Attempt to connect to the upstream server to verify its configuration */ log_info(_("connecting to upstream node\n")); source_conn = establish_db_connection_by_params((const char**)source_conninfo.keywords, (const char**)source_conninfo.values, false); /* * Unless in barman mode, exit with an error; * establish_db_connection_by_params() will have already logged an error message */ if (PQstatus(source_conn) != CONNECTION_OK) { if (mode != barman) { PQfinish(source_conn); exit(ERR_DB_CON); } } else { /* * If a connection was established, perform some sanity checks on the * provided upstream connection */ t_node_info upstream_node_record = T_NODE_INFO_INITIALIZER; int query_result; t_extension_status extension_status; /* Verify that upstream node is a supported server version */ log_verbose(LOG_INFO, _("connected to upstream node, checking its state\n")); server_version_num = check_server_version(source_conn, "master", true, NULL); check_upstream_config(source_conn, server_version_num, true); if (get_cluster_size(source_conn, cluster_size) == false) exit(ERR_DB_QUERY); log_info(_("Successfully connected to source node. Current installation size is %s"), cluster_size); /* * If --recovery-min-apply-delay was passed, check that * we're connected to PostgreSQL 9.4 or later */ // XXX should this be a config file parameter? if (*runtime_options.recovery_min_apply_delay) { if (server_version_num < 90400) { log_error(_("PostgreSQL 9.4 or greater required for --recovery-min-apply-delay\n")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } } /* * If the upstream node is a standby, try to connect to the primary too so we * can write an event record */ if (is_standby(source_conn)) { primary_conn = get_master_connection(source_conn, NULL, NULL); // XXX check this worked? } else { primary_conn = source_conn; } /* * Sanity-check that the master node has a repmgr schema - if not * present, fail with an error unless -F/--force is used (to enable * repmgr to be used as a standalone clone tool) */ extension_status = get_repmgr_extension_status(primary_conn); if (extension_status != REPMGR_INSTALLED) { if (!runtime_options.force) { /* schema doesn't exist */ log_error(_("repmgr extension not found on upstream server")); log_hint(_("check that the upstream server is part of a repmgr cluster")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } log_warning(_("repmgr extension not found on upstream server")); } /* Fetch the source's data directory */ if (get_pg_setting(source_conn, "data_directory", upstream_data_directory) == false) { log_error(_("unable to retrieve upstream node's data directory")); log_hint(_("STANDBY CLONE must be run as a database superuser")); PQfinish(source_conn); exit(ERR_BAD_CONFIG); } /* * If no target directory was explicitly provided, we'll default to * the same directory as on the source host. */ if (local_data_directory_provided == false) { strncpy(local_data_directory, upstream_data_directory, MAXPGPATH); log_notice(_("setting data directory to: \"%s\""), local_data_directory); log_hint(_("use -D/--pgdata to explicitly specify a data directory")); } /* * Copy the source connection so that we have some default values, * particularly stuff like passwords extracted from PGPASSFILE; * these will be overridden from the upstream conninfo, if provided. */ conn_to_param_list(source_conn, &recovery_conninfo); /* * Attempt to find the upstream node record */ if (config_file_options.upstream_node_id == NO_UPSTREAM_NODE) upstream_node_id = get_master_node_id(source_conn); else upstream_node_id = config_file_options.upstream_node_id; query_result = get_node_record(source_conn, upstream_node_id, &upstream_node_record); if (query_result) { upstream_record_found = true; strncpy(recovery_conninfo_str, upstream_node_record.conninfo, MAXLEN); } } }