Skip to content
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

WIP: Revamp config management #1104

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2d282e4
Bump the minimum required channels version to v1.4.0
llucax Nov 22, 2024
b67acd6
Add a `ConfigManager` class
llucax Nov 15, 2024
29725c4
Add an option to wait for the first configuration
llucax Nov 22, 2024
2921ed8
Add option to only send changed configurations
llucax Nov 22, 2024
751d20e
Add support for subscribing to specific config keys
llucax Nov 22, 2024
3ccb4eb
Add support for validating configurations with a schema
llucax Nov 28, 2024
e39cd6b
Add support to filter a sub-key
llucax Nov 25, 2024
3129c42
Assert the receiver has the correct type
llucax Dec 6, 2024
63c96a0
Add support for skipping `None` configs
llucax Dec 6, 2024
77295dc
Add a global instance for the config manager
llucax Nov 22, 2024
cd08feb
Support using `BackgroundService` as a *mixin*
llucax Dec 6, 2024
fbe18de
Add a base config schema that provides quantities support
llucax Dec 6, 2024
3201348
Add a `Reconfigurable` *mixin*
llucax Dec 6, 2024
2878a2a
Make the `LoggingConfigUpdatingActor` `Reconfigurable`
llucax Dec 9, 2024
0b54413
Allow configuring logging via `ConfigManager`
llucax Dec 9, 2024
80fc626
Revert "Make the `LoggingConfigUpdatingActor` `Reconfigurable`"
llucax Dec 10, 2024
c34c199
Revert "Add a `Reconfigurable` *mixin*"
llucax Dec 10, 2024
05164ef
Revert "Support using `BackgroundService` as a *mixin*"
llucax Dec 10, 2024
dcd76fb
Revert "Add a global instance for the config manager"
llucax Dec 10, 2024
9215c15
WIP: Add full example in the `config` module.
llucax Nov 22, 2024
40dcc3f
Revert "Add an option to wait for the first configuration"
llucax Dec 10, 2024
1f96cec
Improve logging for configuration file reading
llucax Dec 10, 2024
61d4b11
Remove support for receiving raw mapping as configuration
llucax Dec 10, 2024
0cc7e94
Move note about update bursts to Skipping superfluous updates
llucax Dec 10, 2024
4189066
Exclude unknown fields from the config by default
llucax Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/frequenz/sdk/config/_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def initialize_config_manager( # pylint: disable=too-many-arguments
/,
*,
force_polling: bool = True,
logging_config_key: str | Sequence[str] | None = "logging",
name: str = "global",
polling_interval: timedelta = timedelta(seconds=5),
wait_for_first_timeout: timedelta = timedelta(seconds=5),
Expand All @@ -37,6 +38,10 @@ def initialize_config_manager( # pylint: disable=too-many-arguments
Args:
config_paths: Paths to the TOML configuration files.
force_polling: Whether to force file polling to check for changes.
logging_config_key: The key to use for the logging configuration. If `None`,
logging configuration will not be managed. If a key is provided, the
manager update the logging configuration whenever the configuration
changes.
name: The name of the config manager.
polling_interval: The interval to poll for changes. Only relevant if
polling is enabled.
Expand All @@ -53,10 +58,11 @@ def initialize_config_manager( # pylint: disable=too-many-arguments
"""
_logger.info(
"Initializing config manager %s for %s with force_polling=%s, "
"polling_interval=%s, wait_for_first_timeout=%s",
"logging_config_key=%s, polling_interval=%s, wait_for_first_timeout=%s",
name,
config_paths,
force_polling,
logging_config_key,
polling_interval,
wait_for_first_timeout,
)
Expand All @@ -69,6 +75,7 @@ def initialize_config_manager( # pylint: disable=too-many-arguments
config_paths,
name=name,
force_polling=force_polling,
logging_config_key=logging_config_key,
polling_interval=polling_interval,
wait_for_first_timeout=wait_for_first_timeout,
auto_start=True,
Expand Down Expand Up @@ -108,14 +115,19 @@ async def shutdown_config_manager(
raise RuntimeError("Config not initialized")

if timeout is None:
_CONFIG_MANAGER.actor.cancel(msg)
_CONFIG_MANAGER.config_actor.cancel(msg)
if _CONFIG_MANAGER.logging_actor:
_CONFIG_MANAGER.logging_actor.cancel(msg)
_logger.info(
"Config manager cancelled, stopping might continue in the background."
)
else:
to_stop = [_CONFIG_MANAGER.config_actor.stop(msg)]
if _CONFIG_MANAGER.logging_actor:
to_stop.append(_CONFIG_MANAGER.logging_actor.stop(msg))
try:
async with asyncio.timeout(timeout.total_seconds()):
await _CONFIG_MANAGER.actor.stop(msg)
await asyncio.gather(*to_stop)
_logger.info("Config manager stopped.")
except asyncio.TimeoutError:
_logger.warning(
Expand Down
21 changes: 18 additions & 3 deletions src/frequenz/sdk/config/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__( # pylint: disable=too-many-arguments
*,
auto_start: bool = True,
force_polling: bool = True,
logging_config_key: str | Sequence[str] | None = "logging",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we will always have this enabled.
Maybe to simplify code we could just enable this by default and not make it configurable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this option adds a lot of complexity, it is just a couple of extra ifs, and gives a bit of extra flexibility to external users that might not want to allow reconfiguring the logging. And if you just want to disable that, it means you cannot use the config manager and miss on a lot of other useful features. Is not like making the new_receiver() method less flexible, where we have a escape hatch (just do manager.config_channel.new_receiver() to create your own), here you can't use the config manager at all if you want to customize only that one small part.

I don't have a strong opinion though, given the trade-offs I prefer for it to stay but I'm also open to remove it (it should be pretty easy to add it back if needed).

name: str | None = None,
polling_interval: timedelta = timedelta(seconds=5),
wait_for_first_timeout: timedelta = timedelta(seconds=5),
Expand All @@ -49,6 +50,10 @@ def __init__( # pylint: disable=too-many-arguments
auto_start: Whether to start the actor automatically. If `False`, the actor
will need to be started manually by calling `start()` on the actor.
force_polling: Whether to force file polling to check for changes.
logging_config_key: The key to use for the logging configuration. If `None`,
logging configuration will not be managed. If a key is provided, the
manager update the logging configuration whenever the configuration
changes.
name: A name to use when creating actors. If `None`, `str(id(self))` will
be used. This is used mostly for debugging purposes.
polling_interval: The interval to poll for changes. Only relevant if
Expand All @@ -66,7 +71,7 @@ def __init__( # pylint: disable=too-many-arguments
)
"""The broadcast channel for the configuration."""

self.actor: Final[ConfigManagingActor] = ConfigManagingActor(
self.config_actor: Final[ConfigManagingActor] = ConfigManagingActor(
config_paths,
self.config_channel.new_sender(),
name=str(self),
Expand All @@ -83,8 +88,17 @@ def __init__( # pylint: disable=too-many-arguments
will be used to wait for the first configuration to be received.
"""

# pylint: disable-next=import-outside-toplevel,cyclic-import
from ._logging_actor import LoggingConfigUpdatingActor

self.logging_actor: Final[LoggingConfigUpdatingActor | None] = (
None if logging_config_key is None else LoggingConfigUpdatingActor()
)

if auto_start:
self.actor.start()
self.config_actor.start()
if self.logging_actor:
self.logging_actor.start()

def __repr__(self) -> str:
"""Return a string representation of this config manager."""
Expand All @@ -93,7 +107,8 @@ def __repr__(self) -> str:
f"name={self.name!r}, "
f"wait_for_first_timeout={self.wait_for_first_timeout!r}, "
f"config_channel={self.config_channel!r}, "
f"actor={self.actor!r}>"
f"logging_actor={self.logging_actor!r}, "
f"config_actor={self.config_actor!r}>"
)

def __str__(self) -> str:
Expand Down