-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for config models #873
Conversation
083a77b
to
a9edd51
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good approach to me - I like the fact that everything is handled in the configuration so all the code in Barman which consumes config remains unchanged, and the implementation is really clean and easy to follow.
I had one minor comment about naming the auto conf file but it's just bikeshedding really.
barman/config.py
Outdated
@@ -750,6 +764,78 @@ def __init__(self, config, name): | |||
if value is not None and value == "" or value == "None": | |||
value = None | |||
setattr(self, key, value) | |||
self._active_model_file = os.path.join( | |||
self.backup_directory, ".active-model.auto.conf" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had assumed we'd store this in the config directory but using backup_directory
like this seems like a better idea - the active model feels more like state rather than configuration so belongs in the server directory. I wonder if we should remove the .conf
suffix so that it looks less like a config file (if it looks like a config file users are more likely to want to edit it directly which we don't want happening here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your thoughts 🙌 . I think that makes sense, yes. I'll perform the following changes:
- Change the "active" file name
- Move the models stuff out of the
servers -> SERVER -> config
key in thebarman diagnose
output - Adjust the unit tests, and include the ones which are missing
a86f516
to
ec19219
Compare
.# Description and examples This commit adds support for config models in Barman. The main idea of this feature is to make it easier to integrate Barman with HA solutions, e.g. Patroni. Besides the usual Barman server configuration, we now add support for Barman model configuration. The model acts as a set of overrides for configuration options for a given Barman server. The servers configuration now have an additional configuration option: * `cluster`: it defines the cluster name for a given Barman server. The name must be unique across different servers configured in Barman. If not filled, it defaults to the server name. In order to say to Barman that a section of the configuration file is a model, you need to set the configuration option `model = true` in such section. Also, as explained for the Barman servers, you need to specify a `cluster` option in the model. That is what ties up the Barman servers with their models. The models currently support only overrides for these configuration options: * `conninfo`; * `primary_conninfo`; * `streaming_conninfo`. As an example, let's assume you have a PostgreSQL cluster composed of 3 nodes, say `A` (primary), `B` (standby), and `C` (standby), and assume you are taking backups from `A` through a Barman server named `my_cluster`. You could create a couple config models which would override the `*conninfo` options of that Barman server. So, in the event of a failover in the cluster, you could ask Barman to apply a given model on top of the Barman server config, overriding the values that would reference the failed node. Please note that you can either have no active model, or have at most one active model at a given point in time. If you have no active model, only the options from the Barman server take place. If you have an active model, the options defined in the model will override the corresponding options from the Barman server config. .# Technical details Previous to this commit we used to have a `ServerConfig` class in the code. It used to have the logic for handling the configuration of the Barman servers. While introducing the models implementation we noticed some overlap between how we would handle server and model configuration. With that in mind we created a `BaseConfig` class which handles the similarities, and created a couple subclasses from it: * `ServerConfig`: the usual `ServerConfig` which existed before, with an additional `cluster` option being supported. Besides that, it now also tracks a few things related with the server models: * The hidden file `.active-model.auto.conf`, which is written under the Barman server directory when a model is active for that server, if any; * A mapping of models which are tied with this Barman server, and which can be activated by the user. * It also exposes a method `apply_model`, which will be used in a future commit, where we will make it possible for users to activate given config model for a server * `to_json` method has been changed, so it correctly points the source file for an option that is override by a model * `ModelConfig`: a new class to handle configuration of Barman models. It exposes a few handy methods: * `get_override_options`: yields the options and values which are set by the model, and which override the Barman server config; * `to_json`: so we can put models information in the diagnose output We had to change the logic for parsing the Barman configuration, so it is now able to distinguish between sections for Barman servers and for Barman models. Besides that, as we need exclusive cluster names among the Barman servers, the function that parses the configuration also checks for that and quits with an error if it finds conflicting names. If everything is fine in regards to cluster names, it will append the config models for each server near the end of parsing. At the very end it will apply any configuration models which are active based on the hidden files that are maintained by Barman inside each server directory. When parsing config models, Barman will log warning messages about options which are not supported in models. Also, it will deactivate Barman servers if they have models which are not properly configured and that are missing the required keys. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
The new command is basically a wrapper around the already implemented code for dealing with switches of configuration through config models. The command will call the `apply_model` method of the server after performing some validations. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
This commit removes bogus attributes from `servers -> SERVER -> config` in the barman diagnose output: * `_active_model_file`: removed * `active_model`: moved to `servers -> SERVER -> active_model` * `models`: moved to `servers -> SERVER -> models` We also changed the `to_json` method of `ModelConfig` to export only configuration options, not class attributes. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
We don't want users to touch that file, so we use an extension which is less likely to lead to that situation: we remove the `.conf` extension. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
ee8c49b
to
ef7dabb
Compare
Signed-off-by: Israel Barth Rubio <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a really clean implementation which does exactly what the PR descriptions says. I made a few suggestions and observations in the comments.
barman/diagnose.py
Outdated
diagnosis["servers"][name]["active_model"] = server.config.active_model | ||
diagnosis["servers"][name]["models"] = {} | ||
for model in server.config.models: | ||
diagnosis["servers"][name]["models"][model.name] = model.to_json( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is failing for me currently because model
is a string - the for loop looks like it should probably be iterating models.values
or models.items
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
Given the recent chatting that we had, we might want to move diagnosis["servers"][name]["models"]
to diagnosis["models"]
too, as they may apply to many Barman servers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
barman/cli.py
Outdated
|
||
if server: | ||
if server.config.active_model == args.model_name: | ||
output.info( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message is really helpful feedback as it makes it clear to the user that the desired configuration is already applied.
I think it would also be nice to have a message for the first time a model is applied - i.e. when running config-switch
on a new configuration when there is not already an active model. Currently, if the first config-switch
results in a change of values then the output will show those changes but if the first applied model happens to match the values in the base configuration then there will be no output all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I think that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be covered by the new behavior of def apply_model(self, name, from_cli=False):
-- I'll submit a commit soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
barman/cli.py
Outdated
) | ||
def config_switch(args): | ||
""" | ||
Switch a Barman server configuration by applying some model on top of it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative wording suggestion: Change the active configuration for a server by applying a named model on top of it
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
barman/config.py
Outdated
"conninfo", | ||
"model", | ||
"primary_conninfo", | ||
"streaming_conninfo", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll want to add the config vars for WAL streaming here though it depends on merge order as to whether that happens in #868 or this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I was about to mention it somewhere too. Whatever PR gets merged later needs to take this into account.
barman/config.py
Outdated
""" | ||
For each Barman server, check for a pre-existing active model. | ||
|
||
If a hidden file with an pre-existing active model file exists, apply |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
with an pre-existing
-> with a pre-existing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
"model_name", | ||
help="specifies the model name for the command ", | ||
), | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a --reset
(or similarly named) option here which would cause Barman to delete the .active-model.audo
file?
This could be useful in scenarios where a user removes a model from the configuration while it is still the active model - currently the only way to resolve this case would be to delete the file manually or to config-swtich
to a different model for the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, makes total sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
barman/config.py
Outdated
"from '%s' to '%s' through the model '%s'" | ||
) % (option, self.name, old_value, value, model.name) | ||
|
||
_logger.debug(message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth making this info
level? I can imagine wanting to see all such changes with the default barman logging configuration, and I would expect the frequency of them to be reasonably low most of the time.
I hadn't understood there were two contexts in which this was called when I first made this comment so I see now why this is logging at debug - it's going to get logged every time the config is loaded, as long as the var in the active model differs from the base config.
Could this be logged at info when the model is applied in the context of barman config-switch
, but debug the rest of the time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I guess that makes sense. We can change the level based on the caller, yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
barman/config.py
Outdated
|
||
setattr(self, option, value) | ||
|
||
with open(self._active_model_file, "w") as f: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the active model gets written back to the file every time the config is generated, even though the value of name
may have been obtained by reading it from that same file a few instructions ago in _apply_models
. It feels like there are really two different responsibilities here
- Applying the model to the live configuration.
- Making the named model the active model by writing it to the file.
When running barman config-switch
both jobs must be carried out, but in all other cases we only need the first. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, yes, that makes sense. The CLI should "apply and persist to disk", while Barman should only "apply".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just submitted a commit with these changes.
Commit with changes suggested in the PR review: - Fix failing `barman diagnose` command: it was failing because we were iterating through the keys of a dictionary instead of through its values - Have messages printed when applying config models, independently if changes have been detected or not. They should go as `INFO` when a user runs `barman config-switch`, and as `DEBUG` when Barman is doing that on its own - Slight change on `barman config-switch` description - Fix typos - Add a `--reset` option to `barman config-switch` command, so users can recover from an active model that doesn't exist anymore without manual intervention on the files - Avoid that Barman internal calls of `apply_model` attempt to rewrite the active model file. That file should only be written when the user runs `barman config-switch` command, when there is an actual change of its value Unit tests were changed accordingly. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
Thanks for the new review! I've applied most of your suggestions -- except for the one which depends on the order of merging between this PR and #868 . I'll now work on adjusting the oversight in regards to cluster vs servers relationship -- it should be 1 to many, not 1 to 1. |
Previous to this commit we had a wrong implementation of the config models: it was considering that cluster x server was a 1-to-1 relationship, and consequently servers x models was a 1-to-many relationship. However, in practice we should be able to have cluster x server as a 1-to-many relationship, and cluster x model as a 1-to-many relationship, in such a way a model could be used for different servers in the same cluster, resulting in a server x model many-to-many relationship. This commit fixes the implementation in that regard. The following changes have been performed: * Implement `manage_model_command`, `get_model` and `get_model_list` in `barman.cli`: this is the equivalent feature from server handling in the CLI, but for models * Move models config from `servers - SERVER -> config -> models` to `models` in the `barman diagnose` output: as cluster is not supposed to be a 1 to 1 relationship with server, it makes more sense to have the models on the root of the output given the same model can be used by different servers * Refactor model related functions from `ServerConfig` and `Config` classes in `barman.config` -- it doesn't make sense anymore to cache models in the `ServerConfig` object, so: * `ServerConfig.add_model` method has been removed * `ServerConfig.apply_model` method now receives a `ModelConfig` instead of a model name * `ServerConfig.to_json` has been adjusted to not expect the `models` attribute * `Config._models` will cache the models * `Config._populate_servers` was renamed to `_populate_servers_and_models`, and it is now responsible for caching the models in `Config`. Also, it doesn't check anymore for cluster exclusiveness among servers as that's not supported to be a 1-to-1 relationship Unit tests were updated accordingly. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
@@ -447,6 +504,7 @@ class ServerConfig(object): | |||
"basebackup_retry_times", | |||
"basebackups_directory", | |||
"check_timeout", | |||
"cluster", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cannot find a good point to add this comment, so adding this here: maybe we want to move this list to the baseconfig class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works, but I guess we should keep it into ServerConfig
because it's not used at all in the BaseConfig
class. I only left PARSERS
in BaseConfig
because that's used in the invoke_parser
method.
Maybe we could "re-create" KEYS
in ModelConfig
using "set of KEYS from ModelConfig except for non-allowed keys", which I guess to be aligned with your other comment about the whitelist vs blacklist in Slack.
What do you think?
References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
…fig` Previous to this commit we were using a whitelist approach to define the configuration options that could be override through config models, and they were: * `conninfo` * `primary_conninfo` * `streaming_conninfo` While discussing within the team we think it would be better if it were more flexible, so we turned that whitelist into a blacklist instead, where we disallow overriding only the value of configuration options which are related with paths for the Barman server, or with hooks for the Barman server, namely: * Path related: * `backup_directory` * `basebackups_directory` * `errors_directory` * `incoming_wals_directory` * `streaming_wals_directory` * `wals_directory` * Hook related: * `post_archive_retry_script` * `post_archive_script` * `post_backup_retry_script` * `post_backup_script` * `post_delete_script` * `post_delete_retry_script` * `post_recovery_retry_script` * `post_recovery_script` * `post_wal_delete_script` * `post_wal_delete_retry_script` * `pre_archive_retry_script` * `pre_archive_script` * `pre_backup_retry_script` * `pre_backup_script` * `pre_delete_script` * `pre_delete_retry_script` * `pre_recovery_retry_script` * `pre_recovery_script` * `pre_wal_delete_script` * `pre_wal_delete_retry_script` That should make it easier to introduce new settings to Barman, as well as make config models more useful for users. References: BAR-122. Signed-off-by: Israel Barth Rubio <[email protected]>
@gcalacoci I just submitted a commit which changes the behavior of |
SonarQube Quality Gate |
Description
This PR adds support for config models in Barman. The main idea of this
feature is to make it easier to integrate Barman with HA solutions, e.g.
Patroni.
Besides the usual Barman server configuration, we now add support for Barman
model configuration. The model acts as a set of overrides for configuration
options for a given Barman server.
The servers configuration now have an additional configuration option:
cluster
: it defines the cluster name for a given Barman server. If not set,defaults to the server name -- for backward compatibility.
In order to say to Barman that a section of the configuration file is a model,
you need to set the configuration option
model = true
in such section. Also,as explained for the Barman servers, you need to specify a
cluster
option inthe model. That is what makes it possible to apply config models to servers:
they need to belong to the same
cluster
.The models currently support overriding all server configuration options, except
for:
Path related options:
Hook related options:
Note: we did it that way because those options can be easily destructive for
a Barman server, with the most concerning ones being the directories related as
they specify where to store files.
Example
As an example, let's assume you have a PostgreSQL cluster composed of 3 nodes,
say
A
(primary),B
(standby), andC
(standby), and assume you are takingbackups from
A
through a Barman server namedmy_cluster
. You could createa couple config models which would override the
conninfo
options of thatBarman server. So, in the event of a failover in the cluster, you could ask
Barman to apply a given model on top of the Barman server config, overriding
the values that would reference the failed node.
Please note that you can either have no active model, or have at most one
active model at a given point in time. If you have no active model, only the
options from the Barman server take place. If you have an active model, the
options defined in the model will override the corresponding options from the
Barman server config.
In order to switch configuration models, the user can use the new CLI
command
barman config-switch
. You need to call the command specifyingthe server name and the model name. That command can also be used with
the
--reset
flag, so the active model is reset, allowing only the server optionsto take place.
Technical details
Previous to this PR we used to have a
ServerConfig
class in the code.It used to have the logic for handling the configuration of the Barman
servers. While introducing the models implementation we noticed some overlap
between how we would handle server and model configuration. With that in mind
we created a
BaseConfig
class which handles the similarities, and created acouple subclasses from it:
ServerConfig
: the usualServerConfig
which existed before, with anadditional
cluster
option being supported. Besides that, it now alsotracks a few things related with the server models:
.active-model.auto
, which is written under theBarman server directory when a model is active for that server, if any;
apply_model
so both Barman and the usercan request a given model to be applied
to_json
method has been changed, so it correctly points the sourcefile for an option that is override by a model
ModelConfig
: a new class to handle configuration of Barman models. Itexposes a few handy methods:
get_override_options
: yields the options and values which are set bythe model, and which override the Barman server config;
to_json
: so we can put models information in the diagnose outputWe had to change the logic for parsing the Barman configuration, so it is
now able to distinguish between sections for Barman servers and for Barman
models. The same function which was used to parse and cache the servers
from config files is not in charge of parsing and caching the config models.
At the very end it will apply any configuration models which are active based
on the hidden files that are maintained by Barman inside each server directory.
When parsing config models, Barman will log warning messages about options
which are not supported in models. Also, it will deactivate Barman servers
if they attempt to apply an invalid model.
References: BAR-122.