From 44a3548c2e96d66c1974c1f980aa1008bc7c1910 Mon Sep 17 00:00:00 2001 From: postgres Date: Wed, 22 Sep 2010 04:40:43 -0500 Subject: [PATCH] Add connection options and a lot of validations, i still have to complete the use of the connection options but the program should still be usable. --- dbutils.c | 73 +++++++++++++ dbutils.h | 3 + repmgr.c | 318 +++++++++++++++++++++++++++++++++++++++++++----------- repmgr.h | 1 + 4 files changed, 330 insertions(+), 65 deletions(-) diff --git a/dbutils.c b/dbutils.c index fd304823..698070af 100644 --- a/dbutils.c +++ b/dbutils.c @@ -56,3 +56,76 @@ is_standby(PGconn *conn) PQclear(res); return result; } + + +bool +is_supported_version(PGconn *conn) +{ + PGresult *res; + int major_version; + + res = PQexec(conn, "SELECT split_part(split_part(version(), ' ', 2), '.', 1)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, "PQexec failed: %s", PQerrorMessage(conn)); + PQclear(res); + } + major_version = atoi(PQgetvalue(res, 0, 0)); + PQclear(res); + + return (major_version >= 9) ? true : false; +} + + +bool +guc_setted(PGconn *conn, const char *parameter, const char *op, const char *value) +{ + PGresult *res; + char sqlquery[8192]; + + sprintf(sqlquery, "SELECT true FROM pg_settings " + " WHERE name = '%s' AND setting %s '%s'", + parameter, op, value); + + res = PQexec(conn, sqlquery); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, "PQexec failed: %s", PQerrorMessage(conn)); + PQclear(res); + } + if (PQgetisnull(res, 0, 0)) + { + PQclear(res); + return false; + } + if (strcmp(PQgetvalue(res, 0, 0), "f") == 0) + { + PQclear(res); + return false; + } + PQclear(res); + + return true; +} + + +char * +get_cluster_size(PGconn *conn) +{ + PGresult *res; + char *size; + char sqlquery[8192]; + + sprintf(sqlquery, "SELECT pg_size_pretty(SUM(pg_database_size(oid))::bigint) " + " FROM pg_database "); + + res = PQexec(conn, sqlquery); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, "PQexec failed: %s", PQerrorMessage(conn)); + PQclear(res); + } + strcpy(size, PQgetvalue(res, 0, 0)) + PQclear(res); + return size; +} diff --git a/dbutils.h b/dbutils.h index e7b5af08..4576c1fc 100644 --- a/dbutils.h +++ b/dbutils.h @@ -6,3 +6,6 @@ PGconn *establishDBConnection(const char *conninfo, const bool exit_on_error); bool is_standby(PGconn *conn); +bool is_supported_version(PGconn *conn); +bool guc_setted(PGconn *conn, const char *parameter, const char *op, const char *value); +char *get_cluster_size(PGconn *conn); diff --git a/repmgr.c b/repmgr.c index 4eecc892..4a1d4366 100644 --- a/repmgr.c +++ b/repmgr.c @@ -3,110 +3,286 @@ * Copyright (c) 2ndQuadrant, 2010 * * Command interpreter for the repmgr - * This module execute isome tasks based on commands and then exit + * This module execute some tasks based on commands and then exit */ +#include "repmgr.h" + #include #include -#include #include - -#include "repmgr.h" +#include #define RECOVERY_FILE "recovery.conf" #define RECOVERY_DONE_FILE "recovery.done" +#define STANDBY_CLONE 1 +#define STANDBY_PROMOTE 2 +#define STANDBY_FOLLOW 3 -void help(void); -void do_standby_clone(char *master); -void do_standby_promote(void); -void do_standby_follow(char *master); +static void help(const char *progname); +static void do_standby_clone(void); +static void do_standby_promote(void); +static void do_standby_follow(void); + +const char *progname; + +const char *keywords[6]; +const char *values[6]; + +const char *dbname = NULL; +char *host = NULL; +char *username = NULL; +const char *dest_dir = NULL; +bool verbose = false; + +int numport = 0; +char *masterport = NULL; +char *standbyport = NULL; + +char *stndby = NULL; +char *stndby_cmd = NULL; int main(int argc, char **argv) { + static struct option long_options[] = { + {"dbname", required_argument, NULL, 'd'}, + {"host", required_argument, NULL, 'h'}, + {"port", required_argument, NULL, 'p'}, + {"username", required_argument, NULL, 'U'}, + {"dest-dir", required_argument, NULL, 'D'}, + {"verbose", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0} + }; - if (argc != 3 && argc != 4) - help(); + int optindex; + int c; + int action; - /* XXX should we check the master pre requisites? */ + progname = get_progname(argv[0]); - - /* Check what is the action we need to execute */ - /* XXX Probably we can do this better but it works for now */ - if (strcasecmp(argv[1], "STANDBY") == 0) + if (argc > 1) { - if (strcasecmp(argv[2], "CLONE") == 0) + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { - /* - * For STANDBY CLONE we should receive the hostname or ip - * of the node being cloned, it should be the third argument - */ - if (argc == 3) - help(); - - do_standby_clone(argv[3]); + help(progname); + exit(0); } - else if (strcasecmp(argv[2], "PROMOTE") == 0) + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { - /* - * For STANDBY PROMOTE we doesn't need any arguments - */ - if (argc == 4) - help(); - do_standby_promote(); + printf("%s (PostgreSQL) " PG_VERSION "\n", progname); + exit(0); } - else if (strcasecmp(argv[2], "FOLLOW") == 0) - { - /* - * For STANDBY FOLLOW we should receive the hostname or ip - * of the node being cloned, it should be the third argument - */ - if (argc == 3) - help(); - do_standby_follow(argv[3]); - } - else - help(); } - else - help(); + + + while ((c = getopt_long(argc, argv, "d:h:p:U:D:v", long_options, &optindex)) != -1) + { + switch (c) + { + case 'd': + dbname = optarg; + break; + case 'h': + host = optarg; + break; + case 'p': + numport++; + switch (numport) + { + case 1: + masterport = optarg; + break; + case 2: + standbyport = optarg; + break; + default: + fprintf(stderr, _("%s: too many parameters of same type; master and standby only\n"), progname); + } + break; + case 'U': + username = optarg; + break; + case 'D': + dest_dir = optarg; + break; + case 'v': + verbose = true; + break; + default: + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + } + + /* Get real command from command line */ + if (optind < argc) + { + stndby = argv[optind++]; + if (strcasecmp(stndby, "STANDBY") != 0) + { + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + } + + if (optind < argc) + { + stndby_cmd = argv[optind++]; + if (strcasecmp(stndby_cmd, "CLONE") == 0) + action = STANDBY_CLONE; + else if (strcasecmp(stndby_cmd, "PROMOTE") == 0) + action = STANDBY_PROMOTE; + else if (strcasecmp(stndby_cmd, "FOLLOW") == 0) + action = STANDBY_FOLLOW; + else + { + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + } + + switch (optind < argc) + { + case 0: + break; + default: + fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), + progname, argv[optind + 1]); + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } + + if (dbname == NULL) + { + if (getenv("PGDATABASE")) + dbname = getenv("PGDATABASE"); + else if (getenv("PGUSER")) + dbname = getenv("PGUSER"); + else + dbname = "postgres"; + } + + if (standbyport == NULL) + standbyport = masterport; + + keywords[2] = "user"; + values[2] = username; + keywords[3] = "dbname"; + values[3] = dbname; + keywords[4] = "application_name"; + values[4] = (char *) progname; + keywords[5] = NULL; + values[5] = NULL; + + switch (action) + { + case STANDBY_CLONE: + do_standby_clone(); + break; + case STANDBY_PROMOTE: + do_standby_promote(); + break; + case STANDBY_FOLLOW: + do_standby_follow(); + break; + default: + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); + exit(1); + } return 0; } -void -do_standby_clone(char *master) +static void +do_standby_clone(void) { PGconn *conn; PGresult *res; char sqlquery[8192]; char script[8192]; - - char master_conninfo[MAXLEN]; int r; char data_dir_full_path[MAXLEN]; char data_dir[MAXLEN]; char recovery_file_path[MAXLEN]; + + char *dummy_file; FILE *recovery_file; + char *first_wal_segment, *last_wal_segment; + char line[MAXLEN]; - sprintf(master_conninfo, "host=%s", master); + /* Connection parameters for master only */ + keywords[0] = "host"; + values[0] = host; + keywords[1] = "port"; + values[1] = masterport; + + /* Can we write in this directory? write a dummy file to test that */ + sprintf(dummy_file, "%s/dummy", ((data_dir == NULL) ? "." : data_dir)); + recovery_file = fopen(dummy_file, "w"); + if (recovery_file == NULL) + { + fprintf(stderr, _("Can't write in this directory, check permissions")); + return; + } + /* If we could write the file, unlink it... it was just a test */ + fclose(recovery_file); + unlink(recovery_file); /* inform the master we will start a backup */ - conn = establishDBConnection(master_conninfo, true); + conn = PQconnectdbParams(keywords, values, true); + if (!conn) + { + fprintf(stderr, _("%s: could not connect to master\n"), + progname); + return; + } + + /* primary should be v9 or better */ + if (!is_supported_version(conn)) + { + PQfinish(conn); + fprintf(stderr, _("%s needs PostgreSQL 9.0 or better\n", progname)); + return; + } /* Check we are cloning a primary node */ if (is_standby(conn)) { - fprintf(stderr, "repmgr: The command should clone a primary node\n"); + PQfinish(conn); + fprintf(stderr, "\nThe command should clone a primary node\n"); return; } + /* And check if it is well configured */ + if (!guc_setted("wal_level", "=", "hot_standby")) + { + PQfinish(conn); + fprintf(stderr, _("%s needs parameter 'wal_level' to be set to 'hot_standby'\n", progname)); + return; + } + if (!guc_setted("wal_keep_segments", "=", "5000")) + { + PQfinish(conn); + fprintf(stderr, _("%s needs parameter 'wal_keep_segments' to be set greater than 0\n", progname)); + return; + } + if (!guc_setted("archive_mode", "=", "on")) + { + PQfinish(conn); + fprintf(stderr, _("%s needs parameter 'archive_mode' to be set to 'on'\n", progname)); + return; + } + + if (verbose) + printf(_("Succesfully connected to primary. Current installation size is %s\n", get_cluster_size(conn))); + fprintf(stderr, "Starting backup...\n"); /* Get the data directory full path and the last subdirectory */ @@ -125,7 +301,7 @@ do_standby_clone(char *master) strcpy(data_dir, PQgetvalue(res, 0, 1)); PQclear(res); - sprintf(sqlquery, "SELECT pg_start_backup('repmgr_standby_clone_%ld')", time(NULL)); + sprintf(sqlquery, "SELECT pg_xlogfile_name(pg_start_backup('repmgr_standby_clone_%ld'))", time(NULL)); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -134,11 +310,13 @@ do_standby_clone(char *master) PQfinish(conn); return; } + strcpy(first_wal_segment, PQgetvalue(res, 0, 0)); PQclear(res); PQfinish(conn); /* rsync data directory to current location */ - sprintf(script, "rsync -r %s:%s .", master, data_dir_full_path); + sprintf(script, "rsync --checksum --keep-dirlinks --compress --progress -r %s:%s %s", + host, data_dir_full_path, ((data_dir == NULL) ? "." : data_dir)); r = system(script); if (r != 0) { @@ -150,11 +328,17 @@ do_standby_clone(char *master) } /* inform the master that we have finished the backup */ - conn = establishDBConnection(master_conninfo, true); + conn = PQconnectdbParams(keywords, values, true); + if (!conn) + { + fprintf(stderr, _("%s: could not connect to master\n"), + progname); + return; + } fprintf(stderr, "Finishing backup...\n"); - sprintf(sqlquery, "SELECT pg_stop_backup()"); + sprintf(sqlquery, "SELECT pg_xlogfile_name(pg_stop_backup())"); res = PQexec(conn, sqlquery); if (PQresultStatus(res) != PGRES_TUPLES_OK) { @@ -163,9 +347,14 @@ do_standby_clone(char *master) PQfinish(conn); return; } + strcpy(last_wal_segment, PQgetvalue(res, 0, 0)); PQclear(res); PQfinish(conn); + if (verbose) + printf(_("%s requires primary to keep WAL files %s until at least %s", + progname, first_wal_segment, last_wal_segment)); + /* Now, if the rsync failed then exit */ if (r != 0) return; @@ -204,7 +393,7 @@ do_standby_clone(char *master) } -void +static void do_standby_promote(void) { char myClusterName[MAXLEN]; @@ -275,8 +464,8 @@ do_standby_promote(void) } -void -do_standby_follow(char *master) +static void +do_standby_follow(void) { char myClusterName[MAXLEN]; int myLocalId = -1; @@ -307,7 +496,7 @@ do_standby_follow(char *master) exit(1); } - sprintf(master_conninfo, "host=%s", master); + sprintf(master_conninfo, "host=%s", host); conn = establishDBConnection(master_conninfo, true); /* Check we are going to point to a primary */ @@ -386,16 +575,15 @@ do_standby_follow(char *master) } -void -help(void) +static void +help(const char *progname) { - fprintf(stderr, "repmgr: Replicator manager \n" + printf(stderr, "\n%s: Replicator manager \n" "This command program performs some tasks like clone a node, promote it " "or making follow another node and then exits.\n" "COMMANDS:\n" "standby clone [node] - allows creation of a new standby\n" "standby promote - allows manual promotion of a specific standby into a " "new master in the event of a failover\n" - "standby follow [node] - allows the standby to re-point itself to a new master\n"); - exit(1); + "standby follow [node] - allows the standby to re-point itself to a new master\n", progname); } diff --git a/repmgr.h b/repmgr.h index ca5343d7..4768f966 100644 --- a/repmgr.h +++ b/repmgr.h @@ -8,6 +8,7 @@ #define _REPMGR_H_ #include "postgres_fe.h" +#include "getopt_long.h" #include "libpq-fe.h" #include "dbutils.h"