diff --git a/Makefile.global.in b/Makefile.global.in
index 83f875ab..775a9996 100644
--- a/Makefile.global.in
+++ b/Makefile.global.in
@@ -22,7 +22,7 @@ GIT_WORK_TREE=${repmgr_abs_srcdir}
GIT_DIR=${repmgr_abs_srcdir}/.git
export GIT_DIR
export GIT_WORK_TREE
-
+PG_LDFLAGS=-lcurl -ljson-c
include $(PGXS)
-include ${repmgr_abs_srcdir}/Makefile.custom
diff --git a/Makefile.in b/Makefile.in
index cf6a5ad5..ef048f3e 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -66,7 +66,7 @@ REPMGR_CLIENT_OBJS = repmgr-client.o \
repmgr-action-primary.o repmgr-action-standby.o repmgr-action-witness.o \
repmgr-action-cluster.o repmgr-action-node.o repmgr-action-service.o repmgr-action-daemon.o \
configdata.o configfile.o configfile-scan.o log.o strutil.o controldata.o dirutil.o compat.o \
- dbutils.o sysutils.o
+ dbutils.o sysutils.o pgbackupapi.o
REPMGRD_OBJS = repmgrd.o repmgrd-physical.o configdata.o configfile.o configfile-scan.o log.o \
dbutils.o strutil.o controldata.o compat.o sysutils.o
diff --git a/configdata.c b/configdata.c
index d40d158a..466cc810 100644
--- a/configdata.c
+++ b/configdata.c
@@ -291,6 +291,46 @@ struct ConfigFileSetting config_file_settings[] =
{},
{}
},
+ /* pg_backupapi_backup_id*/
+ {
+ "pg_backupapi_backup_id",
+ CONFIG_STRING,
+ { .strptr = config_file_options.pg_backupapi_backup_id },
+ { .strdefault = "" },
+ {},
+ { .strmaxlen = sizeof(config_file_options.pg_backupapi_backup_id) },
+ {}
+ },
+ /* pg_backupapi_host*/
+ {
+ "pg_backupapi_host",
+ CONFIG_STRING,
+ { .strptr = config_file_options.pg_backupapi_host },
+ { .strdefault = "" },
+ {},
+ { .strmaxlen = sizeof(config_file_options.pg_backupapi_host) },
+ {}
+ },
+ /* pg_backupapi_node_name */
+ {
+ "pg_backupapi_node_name",
+ CONFIG_STRING,
+ { .strptr = config_file_options.pg_backupapi_node_name },
+ { .strdefault = "" },
+ {},
+ { .strmaxlen = sizeof(config_file_options.pg_backupapi_node_name) },
+ {}
+ },
+ /* pg_backupapi_remote_ssh_command */
+ {
+ "pg_backupapi_remote_ssh_command",
+ CONFIG_STRING,
+ { .strptr = config_file_options.pg_backupapi_remote_ssh_command },
+ { .strdefault = "" },
+ {},
+ { .strmaxlen = sizeof(config_file_options.pg_backupapi_remote_ssh_command) },
+ {}
+ },
/* =======================
* standby follow settings
diff --git a/configfile.h b/configfile.h
index 3bcb9f51..3d6f423a 100644
--- a/configfile.h
+++ b/configfile.h
@@ -164,6 +164,10 @@ typedef struct
char archive_cleanup_command[MAXLEN];
bool use_primary_conninfo_password;
char passfile[MAXPGPATH];
+ char pg_backupapi_backup_id[NAMEDATALEN];
+ char pg_backupapi_host[NAMEDATALEN];
+ char pg_backupapi_node_name[NAMEDATALEN];
+ char pg_backupapi_remote_ssh_command[MAXLEN];
/* standby promote settings */
int promote_check_timeout;
diff --git a/errcode.h b/errcode.h
index 6bc30c78..0bd91ccd 100644
--- a/errcode.h
+++ b/errcode.h
@@ -49,5 +49,6 @@
#define ERR_NODE_STATUS 25
#define ERR_REPMGRD_PAUSE 26
#define ERR_REPMGRD_SERVICE 27
+#define ERR_PGBACKUPAPI_SERVICE 28
#endif /* _ERRCODE_H_ */
diff --git a/pgbackupapi.c b/pgbackupapi.c
new file mode 100644
index 00000000..3b40d34a
--- /dev/null
+++ b/pgbackupapi.c
@@ -0,0 +1,147 @@
+/*
+ * pgbackupapi.c
+ * Copyright (c) EnterpriseDB Corporation, 2010-2021
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include
+#include
+
+#include "repmgr.h"
+#include "pgbackupapi.h"
+
+
+size_t receive_operations_cb(void *content, size_t size, size_t nmemb, char *buffer) {
+ short int max_chars_to_copy = MAX_BUFFER_LENGTH -2;
+ short int i = 0;
+ int operation_length = 0;
+ json_object *value;
+
+ json_object *root = json_tokener_parse(content);
+ json_object *operations = json_object_object_get(root, "operations");
+
+ operation_length = strlen(json_object_get_string(operations));
+ if (operation_length < max_chars_to_copy) {
+ max_chars_to_copy = operation_length;
+ }
+
+ strncpy(buffer, json_object_get_string(operations), max_chars_to_copy);
+
+ fprintf(stdout, "Success! The following operations were found\n");
+ for (i=0; ihost, task->node_name);
+
+ //`url` is freed on the function that called this
+ return url;
+}
+
+CURLcode get_operations_on_server(CURL *curl, operation_task *task) {
+ char buffer[MAX_BUFFER_LENGTH];
+ char *url = define_base_url(task);
+ CURLcode ret;
+
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_operations_cb);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+
+ ret = curl_easy_perform(curl);
+ free(url);
+
+ return ret;
+}
+
+size_t receive_operation_id(void *content, size_t size, size_t nmemb, char *buffer) {
+ json_object *root = json_tokener_parse(content);
+ json_object *operation = json_object_object_get(root, "operation_id");
+
+ if (operation != NULL) {
+ strncpy(buffer, json_object_get_string(operation), MAX_BUFFER_LENGTH-2);
+ }
+
+ return size * nmemb;
+}
+
+
+CURLcode create_new_task(CURL *curl, operation_task *task) {
+ PQExpBufferData payload;
+ char *url = define_base_url(task);
+ CURLcode ret;
+ json_object *root = json_object_new_object();
+ struct curl_slist *chunk = NULL;
+
+ json_object_object_add(root, "operation_type", json_object_new_string(task->operation_type));
+ json_object_object_add(root, "backup_id", json_object_new_string(task->backup_id));
+ json_object_object_add(root, "remote_ssh_command", json_object_new_string(task->remote_ssh_command));
+ json_object_object_add(root, "destination_directory", json_object_new_string(task->destination_directory));
+
+ initPQExpBuffer(&payload);
+ appendPQExpBufferStr(&payload, json_object_to_json_string(root));
+
+ chunk = curl_slist_append(chunk, "Content-type: application/json");
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.data);
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
+ //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_operation_id);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, task->operation_id);
+ ret = curl_easy_perform(curl);
+ free(url);
+ termPQExpBuffer(&payload);
+
+ return ret;
+}
+
+
+size_t receive_operation_status(void *content, size_t size, size_t nmemb, char *buffer) {
+ json_object *root = json_tokener_parse(content);
+ json_object *status = json_object_object_get(root, "status");
+ if (status != NULL) {
+ strncpy(buffer, json_object_get_string(status), MAX_BUFFER_LENGTH-2);
+ }
+ else {
+ fprintf(stderr, "Incorrect reply received for that operation ID.\n");
+ strcpy(buffer, "\0");
+ }
+ return size * nmemb;
+}
+
+CURLcode get_status_of_operation(CURL *curl, operation_task *task) {
+ CURLcode ret;
+ char *url = define_base_url(task);
+
+ strcat(url, "/");
+ strcat(url, task->operation_id);
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receive_operation_status);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, task->operation_status);
+
+ ret = curl_easy_perform(curl);
+ free(url);
+
+ return ret;
+}
diff --git a/pgbackupapi.h b/pgbackupapi.h
new file mode 100644
index 00000000..1f642899
--- /dev/null
+++ b/pgbackupapi.h
@@ -0,0 +1,46 @@
+/*
+ * pgbackupapi.h
+ * Copyright (c) EnterpriseDB Corporation, 2010-2021
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#include
+#include
+
+typedef struct operation_task {
+ char *backup_id;
+ char *destination_directory;
+ char *operation_type;
+ char *operation_id;
+ char *operation_status;
+ char *remote_ssh_command;
+ char *host;
+ char *node_name;
+} operation_task;
+
+//Default simplebuffer size in most of operations
+#define MAX_BUFFER_LENGTH 72
+
+//Callbacks to send/receive data from pg-backup-api endpoints
+size_t receive_operations_cb(void *content, size_t size, size_t nmemb, char *buffer);
+size_t receive_operation_id(void *content, size_t size, size_t nmemb, char *buffer);
+size_t receive_operation_status(void *content, size_t size, size_t nmemb, char *buffer);
+
+//Functions that implement the logic and know what to do and how to comunnicate wuth the API
+CURLcode get_operations_on_server(CURL *curl, operation_task *task);
+CURLcode create_new_task(CURL *curl, operation_task *task);
+CURLcode get_status_of_operation(CURL *curl, operation_task *task);
+
+//Helper to make simpler to read the handler where we set the URL
+char * define_base_url(operation_task *task);
diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c
index cc9829c1..be65de36 100644
--- a/repmgr-action-standby.c
+++ b/repmgr-action-standby.c
@@ -21,6 +21,7 @@
#include
#include
+#include
#include "repmgr.h"
#include "dirutil.h"
@@ -29,7 +30,7 @@
#include "repmgr-client-global.h"
#include "repmgr-action-standby.h"
-
+#include "pgbackupapi.h"
typedef struct TablespaceDataListCell
{
@@ -113,6 +114,7 @@ static void check_recovery_type(PGconn *conn);
static void initialise_direct_clone(t_node_info *local_node_record, t_node_info *upstream_node_record);
static int run_basebackup(t_node_info *node_record);
static int run_file_backup(t_node_info *node_record);
+static int run_pg_backupapi(t_node_info *node_record);
static void copy_configuration_files(bool delete_after_copy);
@@ -687,19 +689,18 @@ do_standby_clone(void)
exit(SUCCESS);
}
- if (mode != barman)
- {
- initialise_direct_clone(&local_node_record, &upstream_node_record);
- }
-
switch (mode)
{
case pg_basebackup:
+ initialise_direct_clone(&local_node_record, &upstream_node_record);
log_notice(_("starting backup (using pg_basebackup)..."));
break;
case barman:
log_notice(_("retrieving backup from Barman..."));
break;
+ case pg_backupapi:
+ log_notice(_("starting backup (using pg_backupapi)..."));
+ break;
default:
/* should never reach here */
log_error(_("unknown clone mode"));
@@ -721,6 +722,9 @@ do_standby_clone(void)
case barman:
r = run_file_backup(&local_node_record);
break;
+ case pg_backupapi:
+ r = run_pg_backupapi(&local_node_record);
+ break;
default:
/* should never reach here */
log_error(_("unknown clone mode"));
@@ -814,7 +818,6 @@ do_standby_clone(void)
}
/* Write the recovery.conf file */
-
if (create_recovery_file(&local_node_record,
&recovery_conninfo,
source_server_version_num,
@@ -846,6 +849,9 @@ do_standby_clone(void)
case barman:
log_notice(_("standby clone (from Barman) complete"));
break;
+ case pg_backupapi:
+ log_notice(_("standby clone (from pg_backupapi) complete"));
+ break;
}
/*
@@ -937,6 +943,9 @@ do_standby_clone(void)
case barman:
appendPQExpBufferStr(&event_details, "barman");
break;
+ case pg_backupapi:
+ appendPQExpBufferStr(&event_details, "pg_backupapi");
+ break;
}
appendPQExpBuffer(&event_details,
@@ -7770,6 +7779,86 @@ stop_backup:
}
+/*
+ * Perform a call to pg_backupapi endpoint to ask barman to write the backup
+ * for us. This will ensure that no matter the format on-disk of new backups,
+ * barman will always find a way how to read and write them.
+ * From repmgr 4 this is only used for Barman backups.
+ */
+static int
+run_pg_backupapi(t_node_info *local_node_record)
+{
+ int r = ERR_PGBACKUPAPI_SERVICE;
+ long http_return_code = 0;
+ short seconds_to_sleep = 3;
+ operation_task *task = malloc(sizeof(operation_task));
+ CURL *curl = curl_easy_init();
+ CURLcode ret;
+
+
+ task->host = malloc(strlen(config_file_options.pg_backupapi_host)+1);
+ task->remote_ssh_command = malloc(strlen(config_file_options.pg_backupapi_remote_ssh_command)+1);
+ task->node_name = malloc(strlen(config_file_options.pg_backupapi_node_name)+1);
+ task->operation_type = malloc(strlen(DEFAULT_STANDBY_PG_BACKUPAPI_OP_TYPE)+1);
+ task->backup_id = malloc(strlen(config_file_options.pg_backupapi_backup_id)+1);
+ task->destination_directory = malloc(strlen(local_data_directory)+1);
+
+ task->operation_id = malloc(MAX_BUFFER_LENGTH);
+ task->operation_status = malloc(MAX_BUFFER_LENGTH);
+
+ strcpy(task->host, config_file_options.pg_backupapi_host);
+ strcpy(task->remote_ssh_command, config_file_options.pg_backupapi_remote_ssh_command);
+ strcpy(task->node_name, config_file_options.pg_backupapi_node_name);
+ strcpy(task->operation_type, DEFAULT_STANDBY_PG_BACKUPAPI_OP_TYPE);
+ strcpy(task->backup_id, config_file_options.pg_backupapi_backup_id);
+ strcpy(task->destination_directory, local_data_directory);
+ strcpy(task->operation_id, "\0");
+
+ ret = create_new_task(curl, task);
+
+ if ((ret != CURLE_OK) || (strlen(task->operation_id) == 0)) {
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_return_code);
+ if (499 > http_return_code && http_return_code >= 400) {
+ log_error("Cannot find backup '%s' for node '%s'.", task->backup_id, task->node_name);
+ } else {
+ log_error("whilst reaching out pg_backup service: %s\n", curl_easy_strerror(ret));
+ }
+ }
+ else
+ {
+ log_info("Success creating the task: operation id '%s'", task->operation_id);
+
+ //We call init again because previous call included POST calls
+ curl_easy_cleanup(curl);
+ curl = curl_easy_init();
+ while (true)
+ {
+ ret = get_status_of_operation(curl, task);
+ if (strlen(task->operation_status) == 0) {
+ log_info("Retrying...");
+ }
+ else
+ {
+ log_info("status %s", task->operation_status);
+ }
+ if (strcmp(task->operation_status, "FAILED") == 0) {
+ break;
+ }
+ if (strcmp(task->operation_status, "DONE") == 0) {
+ r = SUCCESS;
+ break;
+ }
+
+ sleep(seconds_to_sleep);
+ }
+ }
+
+ curl_easy_cleanup(curl);
+ free(task);
+ return r;
+}
+
+
static char *
make_barman_ssh_command(char *buf)
{
diff --git a/repmgr-client-global.h b/repmgr-client-global.h
index c16bb9ea..5ed4f0f2 100644
--- a/repmgr-client-global.h
+++ b/repmgr-client-global.h
@@ -193,7 +193,8 @@ typedef struct
typedef enum
{
barman,
- pg_basebackup
+ pg_basebackup,
+ pg_backupapi
} standy_clone_mode;
typedef enum
diff --git a/repmgr-client.c b/repmgr-client.c
index ab8ca78f..6fa8edd2 100644
--- a/repmgr-client.c
+++ b/repmgr-client.c
@@ -3096,9 +3096,14 @@ get_standby_clone_mode(void)
if (*config_file_options.barman_host != '\0' && runtime_options.without_barman == false)
mode = barman;
- else
- mode = pg_basebackup;
-
+ else {
+ if (*config_file_options.pg_backupapi_host != '\0') {
+ log_info("Attempting to use `pg_backupapi` new restore mode");
+ mode = pg_backupapi;
+ }
+ else
+ mode = pg_basebackup;
+ }
return mode;
}
diff --git a/repmgr.h b/repmgr.h
index d9d231cc..eda3b925 100644
--- a/repmgr.h
+++ b/repmgr.h
@@ -116,6 +116,7 @@
#define DEFAULT_STANDBY_FOLLOW_TIMEOUT 30 /* seconds */
#define DEFAULT_STANDBY_FOLLOW_RESTART false
#define DEFAULT_SHUTDOWN_CHECK_TIMEOUT 60 /* seconds */
+#define DEFAULT_STANDBY_PG_BACKUPAPI_OP_TYPE "recovery"
#define DEFAULT_STANDBY_RECONNECT_TIMEOUT 60 /* seconds */
#define DEFAULT_NODE_REJOIN_TIMEOUT 60 /* seconds */
#define DEFAULT_ARCHIVE_READY_WARNING 16 /* WAL files */