Skip to content

Commit

Permalink
[#329] Implement a command to change a configuration value
Browse files Browse the repository at this point in the history
This commit introduces the `config-set` in `pgagroal-cli` to allow the
user to ask the pooler to change a configuration setting.
As a simple usage example:
    pgagroal-cli config-set log_level debug

The idea is to write over the communication socket the parameter name
and the parameter value, so that the system can understand what has to
do.

In order to achieve the capability to dynamically set a single
parameter, a new utility function named
'pgagroal_appy_main_configuration' has been created. Such
function is now the backbone of the configuration reading process.

Other internal functions have been added to easily manage the dynamic
changes of pieces of structures, e.g., `struct hba` and alike.
The code that loads the initial configuration out of the configuration
files has been refactored in order to use these new utility functions
so to keep the whole system coherent.

When a request to dynamically change a single parameter arrives, the
system creates a clone of the currently running configuration, than
try to apply on such a clone the new setting, and in the case of
success, swaps the cloned configuration with the currently running
one, so that the currently running one becomes the (changed) cloned.
In the case of failure, e.g., if the configuration change cannot be
applied because it requires a restart, the cloned configuration is
destroyed and the system keeps running with the previous one.

The answer of a `config-set` command is a `config-get` for the same
setting, that is the system sends over the communication socket the
value of the setting requested to be changed. If the final value has
changed, the `pgagroal-cli` will receive the new value, otherwise if
the old value is kept, the old value will be sent. This allows
for automation of `pgagroal-cli config-set` usage, so that it becomes
easy to test back when a parameter have been applied.
The `pgagroal-cli` is responsible to understand if the change did take
effect, so it does compare the sent value with the received answer,
and if they are the same it assumes there was an error. In case of
failure, a return error code is returned, otherwise zero on success.

If the `pgagroal-cli` is used with the verbose flag, the ending result
will print out the status of the command and the value of the setting
as done by `config-get`.

This commit also changed some management debug levels to `debug`
consistently between `config-get` and `config-set` implementations.

Documentation updated.
Shell completions updated.

Close #329
  • Loading branch information
fluca1978 authored and jesperpedersen committed Apr 6, 2023
1 parent bd8168b commit 2990214
Show file tree
Hide file tree
Showing 10 changed files with 1,206 additions and 444 deletions.
2 changes: 1 addition & 1 deletion contrib/shell_comp/pgagroal_comp.bash
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pgagroal_cli_completions()
if [ "${#COMP_WORDS[@]}" == "2" ]; then
# main completion: the user has specified nothing at all
# or a single word, that is a command
COMPREPLY=($(compgen -W "flush-idle flush-gracefully flush-all is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get" "${COMP_WORDS[1]}"))
COMPREPLY=($(compgen -W "flush-idle flush-gracefully flush-all is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set" "${COMP_WORDS[1]}"))
fi
}

Expand Down
2 changes: 1 addition & 1 deletion contrib/shell_comp/pgagroal_comp.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function _pgagroal_cli()
{
local line
_arguments -C \
"1: :(flush-idle flush-all flush-gracefully is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get)" \
"1: :(flush-idle flush-all flush-gracefully is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set)" \
"*::arg:->args"
}

Expand Down
49 changes: 49 additions & 0 deletions doc/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,55 @@ Success (0)

If the parameter name specified is not found or invalid, the program `pgagroal-cli` exit normally without printing any value.


## config-set
Allows the setting of a configuration parameter at run-time, if possible.

Examples
```
pgagroal-cli config-set log_level debug
pgagroal-cli config-set server.venkman.port 6432
pgagroal config-set limit.pgbench.max_size 2
```

The syntax for setting parameters is the same as for the command `config-get`, therefore parameters are organized into namespaces:
- `main` (optional) is the main pgagroal configuration namespace, for example `main.log_level` or simply `log_level`;
- `server` is the namespace referred to a specific server. It has to be followed by the name of the server and the name of the parameter to change, in a dotted notation, like `server.venkman.port`;
- `limit` is the namespace referred to a specific limit entry, followed by the name of the username used in the limit entry.

When executed, the `config-set` command returns the run-time setting of the specified parameter: if such parameter is equal to the value supplied, the change has been applied, otherwise it means that the old setting has been kept.
The `--verbose` flag can be used to understand if the change has been applied:

```
$ pgagroal-cli config-set log_level debug
debug
$ pgagroal-cli config-set log_level debug --verbose
log_level = debug
pgagroal-cli: Success (0)
```

When a setting modification cannot be applied, the system returns the "old" setting value and, if `--verbose` is specified, the error indication:

```
$ pgagroal-cli config-set max_connections 100
40
$ pgagroal-cli config-set max_connections 100 --verbose
max_connections = 40
pgagroal-cli: Error (2)
```

When a `config-set` cannot be applied, the system will report in the logs an indication about the problem. With regard to the previous example, the system reports in the logs something like the following (depending on your `log_level`):

```
DEBUG Trying to change main configuration setting <max_connections> to <100>
INFO Restart required for max_connections - Existing 40 New 100
WARN 1 settings cannot be applied
DEBUG pgagroal_management_write_config_set: unable to apply changes to <max_connections> -> <100>
```


## Shell completions

There is a minimal shell completion support for `pgagroal-cli`.
Expand Down
94 changes: 93 additions & 1 deletion src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
#define ACTION_SWITCH_TO 12
#define ACTION_RELOAD 13
#define ACTION_CONFIG_GET 14
#define ACTION_CONFIG_SET 15

static int flush(SSL* ssl, int socket, int32_t mode, char* database);
static int enabledb(SSL* ssl, int socket, char* database);
Expand All @@ -80,6 +81,7 @@ static int reset_server(SSL* ssl, int socket, char* server);
static int switch_to(SSL* ssl, int socket, char* server);
static int reload(SSL* ssl, int socket);
static int config_get(SSL* ssl, int socket, char* config_key, bool verbose);
static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose);

static void
version(void)
Expand Down Expand Up @@ -127,6 +129,7 @@ usage(void)
printf(" reset Reset the Prometheus statistics\n");
printf(" reset-server Reset the state of a server\n");
printf(" config-get Retrieves a configuration value\n");
printf(" config-set Modifies a configuration value\n");
printf("\n");
printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE);
printf("Report bugs: %s\n", PGAGROAL_ISSUES);
Expand Down Expand Up @@ -159,6 +162,7 @@ main(int argc, char** argv)
bool remote_connection = false;
long l_port;
char* config_key = NULL; /* key for a configuration setting */
char* config_value = NULL; /* value for a configuration setting */

while (1)
{
Expand Down Expand Up @@ -445,6 +449,16 @@ main(int argc, char** argv)
action = ACTION_CONFIG_GET;
config_key = argv[argc - 1];
}
else if (argc > 3 && !strncmp("config-set", argv[argc - 3], MISC_LENGTH)
&& strlen(argv[argc - 2]) > 0
&& strlen(argv[argc - 1]) > 0)
{
/* set a configuration value */
action = ACTION_CONFIG_SET;
config_key = argv[argc - 2];
config_value = argv[argc - 1];
}

if (action != ACTION_UNKNOWN)
{
if (!remote_connection)
Expand Down Expand Up @@ -594,6 +608,10 @@ main(int argc, char** argv)
{
exit_code = config_get(s_ssl, socket, config_key, verbose);
}
else if (action == ACTION_CONFIG_SET)
{
exit_code = config_set(s_ssl, socket, config_key, config_value, verbose);
}
}

done:
Expand Down Expand Up @@ -646,7 +664,7 @@ main(int argc, char** argv)

if (verbose)
{
warnx("%s (%d)\n", exit_code == EXIT_STATUS_OK ? "Success" : "Error", exit_code);
warnx("%s (%d)", exit_code == EXIT_STATUS_OK ? "Success" : "Error", exit_code);
}

return exit_code;
Expand Down Expand Up @@ -884,3 +902,77 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose)
error:
return EXIT_STATUS_CONNECTION_ERROR;
}

/**
* Entry point for a config-set command.
*
* The function requires the configuration parameter to set and its value.
* It then sends the command over the socket and reads the answer back.
*
* @param ssl the SSL connection
* @param socket the socket to use
* @param config_key the parameter name to set
* @param config_value the value to set the parameter to
* @param verbose if true the system will print back the new value of the configuration parameter
* @return 0 on success
*/
static int
config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose)
{
char* buffer = NULL;
int status = EXIT_STATUS_DATA_ERROR;

if (!config_key || strlen(config_key) > MISC_LENGTH
|| !config_value || strlen(config_value) > MISC_LENGTH)
{
goto error;
}

if (pgagroal_management_config_set(ssl, socket, config_key, config_value))
{
goto error;
}
else
{
buffer = malloc(MISC_LENGTH);
memset(buffer, 0, MISC_LENGTH);
if (pgagroal_management_read_config_get(socket, &buffer))
{
free(buffer);
goto error;
}

// if the setting we sent is different from the setting we get
// than the system has not applied, so it is an error
if (strncmp(config_value, buffer, MISC_LENGTH) == 0)
{
status = EXIT_STATUS_OK;
}
else
{
status = EXIT_STATUS_DATA_ERROR;
}

// assume an empty response is ok,
// do not throw an error to indicate no configuration
// setting with such name as been found
if (buffer && strlen(buffer))
{
if (verbose)
{
printf("%s = %s\n", config_key, buffer);
}
else
{
printf("%s\n", buffer);
}
}

free(buffer);
return status;
}

return status;
error:
return EXIT_STATUS_CONNECTION_ERROR;
}
51 changes: 51 additions & 0 deletions src/include/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ extern "C" {
*/
#define PGAGROAL_MAIN_INI_SECTION "pgagroal"

/*
* The following constants are used to clearly identify
* a section the user wants to get configuration
* or change. They are used in the config-get
* and config-set operations.
*/
#define PGAGROAL_CONF_SERVER_PREFIX "server"
#define PGAGROAL_CONF_HBA_PREFIX "hba"
#define PGAGROAL_CONF_LIMIT_PREFIX "limit"

/**
* Status that pgagroal_read_configuration() could provide.
* Use only negative values for errors, since a positive return
Expand Down Expand Up @@ -290,6 +300,47 @@ pgagroal_can_prefill(void);
int
pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size);

/**
* Function to apply a single configuration parameter.
*
* This is the backbone function used when parsing the main configuration file
* and is used to set any of the allowed parameters.
*
* @param config the configuration to apply values onto
* @param srv the server to which the configuration parameter refers to, if needed
* @param section the section of the file, main or server
* @param key the parameter name of the configuration
* @param value the value of the configuration
* @return 0 on success
*
* Examples of usage:
* pgagroal_apply_main_configuration( config, NULL, PGAGROAL_MAIN_INI_SECTION, "log_level", "info" );
*/
int
pgagroal_apply_main_configuration(struct configuration* config,
struct server* srv,
char* section,
char* key,
char* value);

/**
* Function to set a configuration value.
*
* This function accepts the same prefixes as the configuration get behavior, so
* a single parameter like 'max_connections' is managed as the main configuration file,
* a 'server' prefix will hit a specific server, a 'limit' prefix will set a limit, and so on.
*
* The idea behind the function is to "clone" the current configuration in use, and then
* apply the changes. In order to be coherent to what a "reload" operation would do,
* this function calls 'pgagroal_transfer_configuration' internally.
*
* @param config_key the string that contains the name of the parameter
* @param config_value the value to set
* @return 0 on success
*/
int
pgagroal_apply_configuration(char* config_key, char* config_value);

#ifdef __cplusplus
}
#endif
Expand Down
38 changes: 38 additions & 0 deletions src/include/management.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extern "C" {
#define MANAGEMENT_RELOAD 18
#define MANAGEMENT_REMOVE_FD 19
#define MANAGEMENT_CONFIG_GET 20
#define MANAGEMENT_CONFIG_SET 21

/**
* Read the management header
Expand Down Expand Up @@ -348,6 +349,43 @@ pgagroal_management_read_config_get(int socket, char** data);
int
pgagroal_management_write_config_get(int socket, char* config_key);

/**
* Management operation: set a configuration setting.
* This function sends over the socket the message to set a configuration
* value.
* In particular, the message block for the action config_set is sent,
* then the size of the configuration parameter to set (e.g., `max_connections`),
* then the parameter name. At this point another couple of "size" and "value" is
* sent with the size of the value to set and its value.
*
* @param ssl the SSL connection
* @param socket the socket file descriptor
* @param config_key the name of the configuration parameter to set
* @param config_value the value to set for the new parameter
* @return 0 on success, 1 on error
*/
int
pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* config_value);

/**
* Function to execute the config-set and write over the socket
* the result.
*
* If the parameter is set, the function calls the
* pgagroal_management_write_config_get to send back over the
* socket the current value of the parameter. Therefore,
* this function answers always back the current value
* so that it is possible to reason about the new value and
* see if it has changed.
*
* @param socket the socket to use for communication
* @param config_key the key to set
* @param config_value the value to use
* @return 0 on success
*/
int
pgagroal_management_write_config_set(int socket, char* config_key, char* config_value);

#ifdef __cplusplus
}
#endif
Expand Down
19 changes: 19 additions & 0 deletions src/include/pgagroal.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ extern "C" {
#define UPDATE_PROCESS_TITLE_MINIMAL 2
#define UPDATE_PROCESS_TITLE_VERBOSE 3

/**
* Constants used to refer to an HBA entry field.
*/
#define PGAGROAL_HBA_ENTRY_TYPE "type"
#define PGAGROAL_HBA_ENTRY_DATABASE "database"
#define PGAGROAL_HBA_ENTRY_USERNAME "username"
#define PGAGROAL_HBA_ENTRY_ADDRESS "address"
#define PGAGROAL_HBA_ENTRY_METHOD "method"

/**
* Constants used to refer to the limit structure fields
*/
#define PGAGROAL_LIMIT_ENTRY_DATABASE "database"
#define PGAGROAL_LIMIT_ENTRY_USERNAME "username"
#define PGAGROAL_LIMIT_ENTRY_MAX_SIZE "max_size"
#define PGAGROAL_LIMIT_ENTRY_MIN_SIZE "min_size"
#define PGAGROAL_LIMIT_ENTRY_INITIAL_SIZE "initial_size"
#define PGAGROAL_LIMIT_ENTRY_LINENO "line_number"

/**
* Constants used to manage the exit code
* of a command sent over the socket in the
Expand Down
Loading

0 comments on commit 2990214

Please sign in to comment.