From 1b30e78c4823655fb92b6c9d9b4ea3b5b0d10679 Mon Sep 17 00:00:00 2001 From: Dimitri Fontaine Date: Tue, 2 Nov 2021 19:20:11 +0100 Subject: [PATCH 1/5] Implement make compose. Similar to `make cluster`, this allows to start a local cluster to play with multiple Postgres nodes registered in a pg_auto_failover monitor. This time the command uses docker-compose to run all the nodes in the background, and each node has its own pre-created external docker volume. --- Dockerfile | 3 +- Makefile | 17 +- src/bin/pg_autoctl/cli_do_root.c | 61 ++ src/bin/pg_autoctl/cli_do_root.h | 5 + src/bin/pg_autoctl/cli_do_tmux.c | 2 +- src/bin/pg_autoctl/cli_do_tmux.h | 1 + src/bin/pg_autoctl/cli_do_tmux_compose.c | 682 +++++++++++++++++++++++ 7 files changed, 767 insertions(+), 4 deletions(-) create mode 100644 src/bin/pg_autoctl/cli_do_tmux_compose.c diff --git a/Dockerfile b/Dockerfile index f333be894..f504b73b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,8 +50,6 @@ RUN apt-get update \ postgresql-common \ && rm -rf /var/lib/apt/lists/* -RUN pip3 install pyroute2>=0.5.17 - RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN echo "deb http://apt.postgresql.org/pub/repos/apt buster-pgdg main" > /etc/apt/sources.list.d/pgdg.list @@ -72,6 +70,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* RUN pip3 install pyroute2>=0.5.17 + RUN adduser --disabled-password --gecos '' docker RUN adduser docker sudo RUN adduser docker postgres diff --git a/Makefile b/Makefile index e125d0c88..ffd4fed03 100644 --- a/Makefile +++ b/Makefile @@ -261,12 +261,27 @@ tmux-session: bin --binpath $(BINPATH) \ --layout $(TMUX_LAYOUT) +tmux-compose-session: + $(PG_AUTOCTL) do tmux compose session \ + --root $(TMUX_TOP_DIR) \ + --first-pgport $(FIRST_PGPORT) \ + --nodes $(NODES) \ + --async-nodes $(NODES_ASYNC) \ + --node-priorities $(NODES_PRIOS) \ + --sync-standbys $(NODES_SYNC_SB) \ + $(CLUSTER_OPTS) \ + --binpath $(BINPATH) \ + --layout $(TMUX_LAYOUT) + cluster: install tmux-clean # This is explicitly not a target, otherwise when make uses multiple jobs # tmux-clean and tmux-session can have a race condidition where tmux-clean # removes the files that are just created by tmux-session. $(MAKE) tmux-session +compose: + $(MAKE) tmux-compose-session + valgrind-session: build-test docker run \ --name $(TEST_CONTAINER_NAME) \ @@ -302,7 +317,7 @@ azdrop: all .PHONY: monitor clean-monitor check-monitor install-monitor .PHONY: bin clean-bin install-bin .PHONY: build-test run-test -.PHONY: tmux-clean cluster +.PHONY: tmux-clean cluster compose .PHONY: azcluster azdrop az .PHONY: build-image .PHONY: build-test-pg10 build-test-pg11 build-test-pg142 diff --git a/src/bin/pg_autoctl/cli_do_root.c b/src/bin/pg_autoctl/cli_do_root.c index bee675026..2b4612654 100644 --- a/src/bin/pg_autoctl/cli_do_root.c +++ b/src/bin/pg_autoctl/cli_do_root.c @@ -221,6 +221,66 @@ CommandLine do_pgsetup_commands = "Manage a local Postgres setup", NULL, NULL, NULL, do_pgsetup); +CommandLine do_tmux_compose_config = + make_command("config", + "Produce a docker-compose configuration file for a demo", + "[option ...]", + " --root path where to create a cluster\n" + " --first-pgport first Postgres port to use (5500)\n" + " --nodes number of Postgres nodes to create (2)\n" + " --async-nodes number of async nodes within nodes (0)\n" + " --node-priorities list of nodes priorities (50)\n" + " --sync-standbys number-sync-standbys to set (0 or 1)\n" + " --skip-pg-hba use --skip-pg-hba when creating nodes\n" + " --layout tmux layout to use (even-vertical)\n" + " --binpath path to the pg_autoctl binary (current binary path)", + cli_do_tmux_script_getopts, + cli_do_tmux_compose_config); + +CommandLine do_tmux_compose_script = + make_command("script", + "Produce a tmux script for a demo or a test case (debug only)", + "[option ...]", + " --root path where to create a cluster\n" + " --first-pgport first Postgres port to use (5500)\n" + " --nodes number of Postgres nodes to create (2)\n" + " --async-nodes number of async nodes within nodes (0)\n" + " --node-priorities list of nodes priorities (50)\n" + " --sync-standbys number-sync-standbys to set (0 or 1)\n" + " --skip-pg-hba use --skip-pg-hba when creating nodes\n" + " --layout tmux layout to use (even-vertical)\n" + " --binpath path to the pg_autoctl binary (current binary path)", + cli_do_tmux_script_getopts, + cli_do_tmux_compose_script); + +CommandLine do_tmux_compose_session = + make_command("session", + "Run a tmux session for a demo or a test case", + "[option ...]", + " --root path where to create a cluster\n" + " --first-pgport first Postgres port to use (5500)\n" + " --nodes number of Postgres nodes to create (2)\n" + " --async-nodes number of async nodes within nodes (0)\n" + " --node-priorities list of nodes priorities (50)\n" + " --sync-standbys number-sync-standbys to set (0 or 1)\n" + " --skip-pg-hba use --skip-pg-hba when creating nodes\n" + " --layout tmux layout to use (even-vertical)\n" + " --binpath path to the pg_autoctl binary (current binary path)", + cli_do_tmux_script_getopts, + cli_do_tmux_compose_session); + +CommandLine *do_tmux_compose[] = { + &do_tmux_compose_config, + &do_tmux_compose_script, + &do_tmux_compose_session, + NULL +}; + +CommandLine do_tmux_compose_commands = + make_command_set("compose", + "Set of facilities to handle docker-compose sessions", + NULL, NULL, NULL, do_tmux_compose); + CommandLine do_tmux_script = make_command("script", "Produce a tmux script for a demo or a test case (debug only)", @@ -290,6 +350,7 @@ CommandLine do_tmux_wait = cli_do_tmux_wait); CommandLine *do_tmux[] = { + &do_tmux_compose_commands, &do_tmux_script, &do_tmux_session, &do_tmux_stop, diff --git a/src/bin/pg_autoctl/cli_do_root.h b/src/bin/pg_autoctl/cli_do_root.h index b125a69dd..b2faedf2f 100644 --- a/src/bin/pg_autoctl/cli_do_root.h +++ b/src/bin/pg_autoctl/cli_do_root.h @@ -109,6 +109,11 @@ void cli_do_tmux_stop(int argc, char **argv); void cli_do_tmux_clean(int argc, char **argv); void cli_do_tmux_wait(int argc, char **argv); +/* src/bin/pg_autoctl/cli_do_tmux_compose.c */ +void cli_do_tmux_compose_config(int argc, char **argv); +void cli_do_tmux_compose_script(int argc, char **argv); +void cli_do_tmux_compose_session(int argc, char **argv); + /* src/bin/pg_autoctl/cli_do_azure.c */ int cli_do_azure_getopts(int argc, char **argv); void cli_do_azure_create_environment(int argc, char **argv); diff --git a/src/bin/pg_autoctl/cli_do_tmux.c b/src/bin/pg_autoctl/cli_do_tmux.c index 5853d75fd..a53eab58c 100644 --- a/src/bin/pg_autoctl/cli_do_tmux.c +++ b/src/bin/pg_autoctl/cli_do_tmux.c @@ -1,5 +1,5 @@ /* - * src/bin/pg_autoctl/cli_do_misc.c + * src/bin/pg_autoctl/cli_do_tmux.c * Implementation of a CLI which lets you run operations on the local * postgres server directly. * diff --git a/src/bin/pg_autoctl/cli_do_tmux.h b/src/bin/pg_autoctl/cli_do_tmux.h index bf5f72dc3..8ff6d5065 100644 --- a/src/bin/pg_autoctl/cli_do_tmux.h +++ b/src/bin/pg_autoctl/cli_do_tmux.h @@ -56,6 +56,7 @@ typedef struct TmuxNodeArray TmuxNode nodes[MAX_NODES]; } TmuxNodeArray; +extern char *tmux_banner[]; extern TmuxOptions tmuxOptions; extern TmuxNodeArray tmuxNodeArray; diff --git a/src/bin/pg_autoctl/cli_do_tmux_compose.c b/src/bin/pg_autoctl/cli_do_tmux_compose.c new file mode 100644 index 000000000..ef8e7ce36 --- /dev/null +++ b/src/bin/pg_autoctl/cli_do_tmux_compose.c @@ -0,0 +1,682 @@ +/* + * src/bin/pg_autoctl/cli_do_tmux_compose.c + * Implementation of a CLI which lets you run operations on a local + * docker-compose environment with multiple Postgres nodes. + * + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the PostgreSQL License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +#include "postgres_fe.h" +#include "pqexpbuffer.h" +#include "snprintf.h" + +#include "cli_common.h" +#include "cli_do_root.h" +#include "cli_do_tmux.h" +#include "cli_root.h" +#include "commandline.h" +#include "config.h" +#include "env_utils.h" +#include "log.h" +#include "pidfile.h" +#include "signals.h" +#include "string_utils.h" + +#include "runprogram.h" + +char *tmux_compose_banner[] = { + "# to quit tmux: type either `Ctrl+b d` or `tmux detach`", + "# to test failover: docker-compose exec monitor pg_autoctl perform failover", + NULL +}; + + +static void prepare_tmux_compose_script(TmuxOptions *options, PQExpBuffer script); +static void prepare_tmux_compose_config(TmuxOptions *options, PQExpBuffer script); + +static void tmux_compose_create_volumes(TmuxOptions *options); +static void tmux_compose_docker_build(TmuxOptions *options); +static bool tmux_compose_down(TmuxOptions *options); + + +/* + * prepare_tmux_compose_script prepares a script for a tmux session with the + * given nodes, root directory, first pgPort, and layout. + */ +static void +prepare_tmux_compose_script(TmuxOptions *options, PQExpBuffer script) +{ + char sessionName[BUFSIZE] = { 0 }; + + sformat(sessionName, BUFSIZE, "pgautofailover-%d", options->firstPort); + + tmux_add_command(script, "set-option -g default-shell /bin/bash"); + tmux_add_command(script, "new-session -s %s", sessionName); + + /* change to the user given options->root directory */ + tmux_add_send_keys_command(script, "cd \"%s\"", options->root); + + /* docker-compose */ + tmux_add_send_keys_command(script, "docker-compose up -d"); + tmux_add_send_keys_command(script, "docker-compose logs -f"); + + /* add a window for pg_autoctl show state */ + tmux_add_command(script, "split-window -v"); + tmux_add_command(script, "select-layout even-vertical"); + + /* wait for the docker volumes to be initialized in docker-compose up -d */ + tmux_add_send_keys_command(script, "sleep 5"); + tmux_add_send_keys_command(script, + "docker-compose exec monitor " + "pg_autoctl watch"); + + /* add a window for interactive pg_autoctl commands */ + tmux_add_command(script, "split-window -v"); + tmux_add_command(script, "select-layout even-vertical"); + + if (options->numSync != -1) + { + tmux_add_send_keys_command( + script, + "docker-compose exec monitor " + "pg_autoctl set formation number-sync-standbys %d", + options->numSync); + } + + /* now select our target layout */ + tmux_add_command(script, "select-layout %s", options->layout); + + if (env_exists("TMUX_EXTRA_COMMANDS")) + { + char extra_commands[BUFSIZE] = { 0 }; + + char *extraLines[BUFSIZE]; + int lineNumber = 0; + + if (!get_env_copy("TMUX_EXTRA_COMMANDS", extra_commands, BUFSIZE)) + { + /* errors have already been logged */ + exit(EXIT_CODE_INTERNAL_ERROR); + } + + int lineCount = splitLines(extra_commands, extraLines, BUFSIZE); + + for (lineNumber = 0; lineNumber < lineCount; lineNumber++) + { + appendPQExpBuffer(script, "%s\n", extraLines[lineNumber]); + } + } + + for (int i = 0; tmux_compose_banner[i] != NULL; i++) + { + tmux_add_send_keys_command(script, "%s", tmux_compose_banner[i]); + } +} + + +/* + * tmux_compose_add_monitor adds a docker-compose service for the monitor node. + */ +static void +tmux_compose_add_monitor(PQExpBuffer script) +{ + char currentWorkingDirectory[MAXPGPATH] = { 0 }; + + if (getcwd(currentWorkingDirectory, MAXPGPATH) == NULL) + { + log_error("Failed to get the current working directory: %m"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + appendPQExpBuffer(script, " monitor:\n"); + appendPQExpBuffer(script, " build: %s\n", currentWorkingDirectory); + appendPQExpBuffer(script, " hostname: monitor\n"); + appendPQExpBuffer(script, " volumes:\n"); + appendPQExpBuffer(script, " - monitor_data:/var/lib/postgres:rw\n"); + appendPQExpBuffer(script, " environment:\n"); + appendPQExpBuffer(script, " PGDATA: /var/lib/postgres/pgaf\n"); + appendPQExpBuffer(script, " expose:\n"); + appendPQExpBuffer(script, " - 5432\n"); + appendPQExpBuffer(script, " command: " + "pg_autoctl create monitor " + " --ssl-self-signed --auth trust --run\n"); +} + + +/* + * tmux_compose_add_monitor adds a docker-compose service for the given + * Postgres node. + */ +static void +tmux_compose_add_node(PQExpBuffer script, + const char *name, + const char *pguser, + const char *dbname, + const char *monitor_pguri) +{ + char currentWorkingDirectory[MAXPGPATH] = { 0 }; + + if (getcwd(currentWorkingDirectory, MAXPGPATH) == NULL) + { + log_error("Failed to get the current working directory: %m"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + appendPQExpBuffer(script, " %s:\n", name); + appendPQExpBuffer(script, " build: %s\n", currentWorkingDirectory); + appendPQExpBuffer(script, " hostname: %s\n", name); + appendPQExpBuffer(script, " volumes:\n"); + appendPQExpBuffer(script, " - %s_data:/var/lib/postgres:rw\n", name); + appendPQExpBuffer(script, " environment:\n"); + appendPQExpBuffer(script, " PGDATA: /var/lib/postgres/pgaf\n"); + appendPQExpBuffer(script, " PGUSER: %s\n", pguser); + appendPQExpBuffer(script, " PGDATABASE: %s\n", dbname); + appendPQExpBuffer(script, " PG_AUTOCTL_MONITOR: \"%s\"\n", monitor_pguri); + appendPQExpBuffer(script, " expose:\n"); + appendPQExpBuffer(script, " - 5432\n"); + appendPQExpBuffer(script, " command: " + "pg_autoctl create postgres " + " --name %s " + " --ssl-self-signed --auth trust --pg-hba-lan --run\n", + name); +} + + +/* + * tmux_compose_add_volume adds a docker-compose volume for the given node + * name. + */ +static void +tmux_compose_add_volume(PQExpBuffer script, const char *name) +{ + appendPQExpBuffer(script, " %s_data:\n", name); + appendPQExpBuffer(script, " external: true\n"); + appendPQExpBuffer(script, " name: vol_%s\n", name); +} + + +/* + * prepare_tmux_compose_config prepares a docker-compose configuration for a + * docker-compose session with the given nodes specifications. + */ +static void +prepare_tmux_compose_config(TmuxOptions *options, PQExpBuffer script) +{ + /* that's optional, but we still fill it as an header of sorts */ + appendPQExpBuffer(script, "version: \"3.9\"\n"); + appendPQExpBuffer(script, "\n"); + + appendPQExpBuffer(script, "services:\n"); + + /* first, the monitor */ + (void) tmux_compose_add_monitor(script); + + /* then, loop over the nodes for the services */ + for (int i = 0; i < tmuxNodeArray.count; i++) + { + TmuxNode *node = &(tmuxNodeArray.nodes[i]); + + (void) tmux_compose_add_node( + script, + node->name, + "demo", + "demo", + "postgresql://autoctl_node@monitor/pg_auto_failover"); + } + + appendPQExpBuffer(script, "\n"); + appendPQExpBuffer(script, "volumes:\n"); + + /* then, loop over the nodes for the volumes */ + (void) tmux_compose_add_volume(script, "monitor"); + + for (int i = 0; i < tmuxNodeArray.count; i++) + { + TmuxNode *node = &(tmuxNodeArray.nodes[i]); + + (void) tmux_compose_add_volume(script, node->name); + } +} + + +/* + * log_program_output logs program output as separate lines and with a prefix. + */ +static void +log_program_output(const char *prefix, Program *program) +{ + if (program->stdOut) + { + char *outLines[BUFSIZE] = { 0 }; + int lineCount = splitLines(program->stdOut, outLines, BUFSIZE); + int lineNumber = 0; + + for (lineNumber = 0; lineNumber < lineCount; lineNumber++) + { + log_info("%s: %s", prefix, outLines[lineNumber]); + } + } + + if (program->stdErr) + { + char *errLines[BUFSIZE] = { 0 }; + int lineCount = splitLines(program->stdOut, errLines, BUFSIZE); + int lineNumber = 0; + + for (lineNumber = 0; lineNumber < lineCount; lineNumber++) + { + log_error("%s: %s", prefix, errLines[lineNumber]); + } + } +} + + +/* + * tmux_compose_docker_build calls `docker-compose build`. + */ +static void +tmux_compose_docker_build(TmuxOptions *options) +{ + if (chdir(options->root) != 0) + { + log_fatal("Failed to change to directory \"%s\": %m", options->root); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + log_info("docker-compose build"); + + char dockerCompose[MAXPGPATH] = { 0 }; + + if (!search_path_first("docker-compose", dockerCompose, LOG_ERROR)) + { + log_fatal("Failed to find program docker-compose in PATH"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + char *args[16]; + int argsIndex = 0; + + args[argsIndex++] = (char *) dockerCompose; + args[argsIndex++] = "build"; + args[argsIndex++] = "--quiet"; + args[argsIndex] = NULL; + + Program program = { 0 }; + + (void) initialize_program(&program, args, false); + + program.capture = false; /* don't capture output */ + program.tty = true; /* allow sharing the parent's tty */ + + char command[BUFSIZE] = { 0 }; + + (void) snprintf_program_command_line(&program, command, BUFSIZE); + + char currentWorkingDirectory[MAXPGPATH] = { 0 }; + + if (getcwd(currentWorkingDirectory, MAXPGPATH) == NULL) + { + log_error("Failed to get the current working directory: %m"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + /* make it easy for the users to reproduce errors if any */ + log_info("cd \"%s\"", currentWorkingDirectory); + log_info("%s", command); + + (void) execute_subprogram(&program); + free_program(&program); + + int returnCode = program.returnCode; + + if (returnCode != 0) + { + log_fatal("Failed to run docker-compose build"); + exit(EXIT_CODE_INTERNAL_ERROR); + } +} + + +/* + * tmux_compose_create_volume calls `docker volume create` for a given volume + * that is going to be referenced in the docker-compose file. + */ +static void +tmux_compose_create_volume(const char *docker, const char *nodeName) +{ + char volumeName[BUFSIZE] = { 0 }; + + sformat(volumeName, sizeof(volumeName), "vol_%s", nodeName); + + log_info("Create docker volume \"%s\"", volumeName); + + Program program = + run_program(docker, "volume", "create", volumeName, NULL); + + if (program.returnCode != 0) + { + char command[BUFSIZE] = { 0 }; + + (void) snprintf_program_command_line(&program, command, BUFSIZE); + + log_error("%s [%d]", command, program.returnCode); + log_program_output("docker volume create", &program); + + log_fatal("Failed to create docker volume: \"%s\"", volumeName); + free_program(&program); + exit(EXIT_CODE_INTERNAL_ERROR); + } +} + + +/* + * tmux_compose_create_volumes calls `docker volume create` for each volume + * that is going to be referenced in the docker-compose file. + */ +static void +tmux_compose_create_volumes(TmuxOptions *options) +{ + char docker[MAXPGPATH] = { 0 }; + + if (!search_path_first("docker", docker, LOG_ERROR)) + { + log_fatal("Failed to find program docker in PATH"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + (void) tmux_compose_create_volume(docker, "monitor"); + + for (int i = 0; i < tmuxNodeArray.count; i++) + { + TmuxNode *node = &(tmuxNodeArray.nodes[i]); + + (void) tmux_compose_create_volume(docker, node->name); + } +} + + +/* + * tmux_compose_rm_volume calls `docker volume rm` for a given volume that has + * been referenced in the docker-compose file. + */ +static void +tmux_compose_rm_volume(const char *docker, const char *nodeName) +{ + char volumeName[BUFSIZE] = { 0 }; + + sformat(volumeName, sizeof(volumeName), "vol_%s", nodeName); + + log_info("Remove docker volume \"%s\"", volumeName); + + Program program = + run_program(docker, "volume", "rm", volumeName, NULL); + + if (program.returnCode != 0) + { + char command[BUFSIZE] = { 0 }; + + (void) snprintf_program_command_line(&program, command, BUFSIZE); + + log_error("%s [%d]", command, program.returnCode); + log_program_output("docker volume rm", &program); + + log_fatal("Failed to remove docker volume: \"%s\"", volumeName); + free_program(&program); + exit(EXIT_CODE_INTERNAL_ERROR); + } +} + + +/* + * tmux_compose_down runs `docker-compose down` and then removes the external + * docker compose volumes that have been created for this run. + */ +static bool +tmux_compose_down(TmuxOptions *options) +{ + char dockerCompose[MAXPGPATH] = { 0 }; + + if (!search_path_first("docker-compose", dockerCompose, LOG_ERROR)) + { + log_fatal("Failed to find program docker-compose in PATH"); + return false; + } + + /* first docker-compose down */ + log_info("docker-compose down"); + + Program program = + run_program(dockerCompose, "down", + "--volumes", "--remove-orphans", NULL); + + if (program.returnCode != 0) + { + char command[BUFSIZE] = { 0 }; + + (void) snprintf_program_command_line(&program, command, BUFSIZE); + + log_error("%s [%d]", command, program.returnCode); + log_program_output("docker-compose down", &program); + + log_fatal("Failed to run docker-compose down"); + free_program(&program); + + return false; + } + + /* + * Now remove all the docker volumes + */ + char docker[MAXPGPATH] = { 0 }; + + if (!search_path_first("docker", docker, LOG_ERROR)) + { + log_fatal("Failed to find program docker in PATH"); + return false; + } + + (void) tmux_compose_rm_volume(docker, "monitor"); + + for (int i = 0; i < tmuxNodeArray.count; i++) + { + TmuxNode *node = &(tmuxNodeArray.nodes[i]); + + (void) tmux_compose_rm_volume(docker, node->name); + } + + return true; +} + + +/* + * keeper_cli_tmux_compose_config generates a docker-compose configuration to + * run a test case or a demo for pg_auto_failover easily, based on using + * docker-compose. + */ +void +cli_do_tmux_compose_config(int argc, char **argv) +{ + TmuxOptions options = tmuxOptions; + PQExpBuffer script = createPQExpBuffer(); + + (void) tmux_process_options(&options); + + if (script == NULL) + { + log_error("Failed to allocate memory"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + /* prepare the tmux script */ + (void) prepare_tmux_compose_config(&options, script); + + /* memory allocation could have failed while building string */ + if (PQExpBufferBroken(script)) + { + log_error("Failed to allocate memory"); + destroyPQExpBuffer(script); + + exit(EXIT_CODE_INTERNAL_ERROR); + } + + fformat(stdout, "%s", script->data); + + destroyPQExpBuffer(script); +} + + +/* + * keeper_cli_tmux_compose_script generates a tmux script to run a test case or + * a demo for pg_auto_failover easily, based on using docker-compose. + */ +void +cli_do_tmux_compose_script(int argc, char **argv) +{ + TmuxOptions options = tmuxOptions; + PQExpBuffer script = createPQExpBuffer(); + + (void) tmux_process_options(&options); + + if (script == NULL) + { + log_error("Failed to allocate memory"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + /* prepare the tmux script */ + (void) prepare_tmux_compose_script(&options, script); + + /* memory allocation could have failed while building string */ + if (PQExpBufferBroken(script)) + { + log_error("Failed to allocate memory"); + destroyPQExpBuffer(script); + + exit(EXIT_CODE_INTERNAL_ERROR); + } + + fformat(stdout, "%s", script->data); + + destroyPQExpBuffer(script); +} + + +/* + * cli_do_tmux_session starts an interactive tmux session with the given + * specifications for a cluster. When the session is detached, the pg_autoctl + * processes are stopped. + */ +void +cli_do_tmux_compose_session(int argc, char **argv) +{ + TmuxOptions options = tmuxOptions; + PQExpBuffer script = createPQExpBuffer(); + PQExpBuffer config = createPQExpBuffer(); + + char scriptPathname[MAXPGPATH] = { 0 }; + char configPathname[MAXPGPATH] = { 0 }; + + bool success = true; + + (void) tmux_process_options(&options); + + /* + * Prepare the tmux script. + */ + if (script == NULL || config == NULL) + { + log_error("Failed to allocate memory"); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + /* prepare the tmux script and docker-compose config */ + (void) prepare_tmux_compose_script(&options, script); + (void) prepare_tmux_compose_config(&options, config); + + /* memory allocation could have failed while building string */ + if (PQExpBufferBroken(script) || PQExpBufferBroken(config)) + { + log_error("Failed to allocate memory"); + destroyPQExpBuffer(script); + destroyPQExpBuffer(config); + + exit(EXIT_CODE_INTERNAL_ERROR); + } + + /* + * Write the config to file. + */ + sformat(configPathname, sizeof(configPathname), + "%s/docker-compose.yml", options.root); + + log_info("Writing docker-compose configuration at \"%s\"", configPathname); + + if (!write_file(config->data, config->len, configPathname)) + { + log_fatal("Failed to write docker-compose config at \"%s\"", + configPathname); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + destroyPQExpBuffer(config); + + /* + * Write the script to file. + */ + sformat(scriptPathname, sizeof(scriptPathname), + "%s/script-%d.tmux", options.root, options.firstPort); + + log_info("Writing tmux session script \"%s\"", scriptPathname); + + if (!write_file(script->data, script->len, scriptPathname)) + { + log_fatal("Failed to write tmux script at \"%s\"", scriptPathname); + exit(EXIT_CODE_INTERNAL_ERROR); + } + + destroyPQExpBuffer(script); + + /* + * Before starting a tmux session, we have to: + * 1. docker-compose build + * 2. create all the volumes + */ + (void) tmux_compose_docker_build(&options); + (void) tmux_compose_create_volumes(&options); + + /* + * Start a tmux session from the script. + */ + if (!tmux_start_server(scriptPathname, options.binpath)) + { + success = false; + log_fatal("Failed to start the tmux session, see above for details"); + } + + /* + * Stop our pg_autoctl processes and kill the tmux session. + */ + log_info("tmux session ended: kill pg_autoct processes"); + success = success && tmux_compose_down(&options); + success = success && tmux_kill_session(&options); + + if (!success) + { + exit(EXIT_CODE_INTERNAL_ERROR); + } +} From 207776710afe8004fcde456bc1972d02f2a38fad Mon Sep 17 00:00:00 2001 From: Dimitri Fontaine Date: Wed, 3 Nov 2021 11:37:48 +0100 Subject: [PATCH 2/5] Use --build-arg PGVERSION=... in make compose. --- src/bin/pg_autoctl/cli_do_tmux_compose.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/bin/pg_autoctl/cli_do_tmux_compose.c b/src/bin/pg_autoctl/cli_do_tmux_compose.c index ef8e7ce36..0e82f9d0f 100644 --- a/src/bin/pg_autoctl/cli_do_tmux_compose.c +++ b/src/bin/pg_autoctl/cli_do_tmux_compose.c @@ -308,11 +308,33 @@ tmux_compose_docker_build(TmuxOptions *options) exit(EXIT_CODE_INTERNAL_ERROR); } + char pgversion[5] = { 0 }; + char pgversionArg[15] = { 0 }; + + if (env_exists("PGVERSION")) + { + if (!get_env_copy("PGVERSION", pgversion, sizeof(pgversion))) + { + /* errors have already been logged */ + log_warn("Using PGVERSION=14"); + strlcpy(pgversion, "14", sizeof(pgversion)); + } + } + else + { + strlcpy(pgversion, "14", sizeof(pgversion)); + } + + /* prepare our --build-arg PGVERSION=XX */ + sformat(pgversionArg, sizeof(pgversionArg), "PGVERSION=%s", pgversion); + char *args[16]; int argsIndex = 0; args[argsIndex++] = (char *) dockerCompose; args[argsIndex++] = "build"; + args[argsIndex++] = "--build-arg"; + args[argsIndex++] = (char *) pgversionArg; args[argsIndex++] = "--quiet"; args[argsIndex] = NULL; From cc4394b5901ecb7776f584cfa0b556eb392bfdb5 Mon Sep 17 00:00:00 2001 From: Dimitri Fontaine Date: Thu, 4 Nov 2021 13:03:17 +0100 Subject: [PATCH 3/5] Introduce another needed wait point in the tmux script. --- src/bin/pg_autoctl/cli_do_tmux_compose.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bin/pg_autoctl/cli_do_tmux_compose.c b/src/bin/pg_autoctl/cli_do_tmux_compose.c index 0e82f9d0f..188f92dd2 100644 --- a/src/bin/pg_autoctl/cli_do_tmux_compose.c +++ b/src/bin/pg_autoctl/cli_do_tmux_compose.c @@ -91,6 +91,8 @@ prepare_tmux_compose_script(TmuxOptions *options, PQExpBuffer script) if (options->numSync != -1) { + /* wait for the monitor to be up-and-running */ + tmux_add_send_keys_command(script, "sleep 10"); tmux_add_send_keys_command( script, "docker-compose exec monitor " From 46d1b66b0cad496bf6576dbc70f21265cd1a5798 Mon Sep 17 00:00:00 2001 From: Dimitri Fontaine Date: Thu, 4 Nov 2021 15:15:16 +0100 Subject: [PATCH 4/5] Clean-up: we don't need both --name and PG_AUTOCTL_NODE_NAME. --- src/bin/pg_autoctl/cli_do_tmux_compose.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bin/pg_autoctl/cli_do_tmux_compose.c b/src/bin/pg_autoctl/cli_do_tmux_compose.c index 188f92dd2..9488412ac 100644 --- a/src/bin/pg_autoctl/cli_do_tmux_compose.c +++ b/src/bin/pg_autoctl/cli_do_tmux_compose.c @@ -193,9 +193,8 @@ tmux_compose_add_node(PQExpBuffer script, appendPQExpBuffer(script, " - 5432\n"); appendPQExpBuffer(script, " command: " "pg_autoctl create postgres " - " --name %s " - " --ssl-self-signed --auth trust --pg-hba-lan --run\n", - name); + "--ssl-self-signed --auth trust " + "--pg-hba-lan --run\n"); } From 9f006f4aab507fcf2b70fb36a76056ac6986c912 Mon Sep 17 00:00:00 2001 From: Jelte Fennema Date: Thu, 23 Dec 2021 13:36:46 +0100 Subject: [PATCH 5/5] Ignore some files for docker --- .dockerignore | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 58 insertions(+) diff --git a/.dockerignore b/.dockerignore index 960ee84b8..e96538fd6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,60 @@ src/bin/pg_autoctl/pg_autoctl docs Dockerfile + +# Global excludes across all subdirectories +*.o +*.bc +*.so +*.so.[0-9] +*.so.[0-9].[0-9] +*.sl +*.sl.[0-9] +*.sl.[0-9].[0-9] +*.dylib +*.dll +*.a +*.mo +*.pot +objfiles.txt +.deps/ +*.gcno +*.gcda +*.gcov +*.gcov.out +lcov.info +coverage/ +*.vcproj +*.vcxproj +win32ver.rc +*.exe +lib*dll.def +lib*.pc +*.log + +# Local excludes in root directory +/config.log +/config.status +/pgsql.sln +/pgsql.sln.cache +/Debug/ +/Release/ +/autom4te.cache +/Makefile.global +/src/Makefile.custom +/tests/__pycache__/ +/env/ + +# Exclude generated SQL files +pgautofailover--?.?.sql +!pgautofailover--1.0.sql + +# Exclude generated PDF and PNG files for the graphics +docs/tikz/*.pdf +docs/tikz/*.png + +# Exclude our demo/test tmux directory +tmux/ +valgrind/* + +.ccls-cache/ diff --git a/.gitignore b/.gitignore index c015e02a9..3d0a5f4fa 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ win32ver.rc *.exe lib*dll.def lib*.pc +*.log # Local excludes in root directory /config.log