blob: ae34d35646f6fcb8feb399dde966f1b418b0b5ab [file]
/** @file
*
* Config Registry - Centralized configuration management
*
* Provides:
* - Registration of config handlers by key
* - Flexible trigger attachment (at registration or later)
* - RPC reload support (YAML content supplied via RPC)
* - Runtime lookup for RPC handlers
*
* @section license License
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <functional>
#include <initializer_list>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <yaml-cpp/yaml.h>
#include "iocore/eventsystem/Lock.h" // For Ptr<ProxyMutex>
#include "mgmt/config/ConfigContext.h"
#include "swoc/Errata.h"
namespace config
{
/// Type of configuration file
enum class ConfigType {
YAML, ///< Modern YAML config (ip_allow.yaml, sni.yaml, etc.)
LEGACY ///< Legacy .config files (remap.config, etc.)
};
/// Declares what content sources a config handler supports.
/// @note If more sources are needed (e.g., Plugin, Env), consider
/// converting to bitwise flags instead of adding combinatorial values.
enum class ConfigSource {
FileOnly, ///< Handler only reloads from file on disk
RecordOnly, ///< Handler only reacts to record changes (no file, no RPC content)
FileAndRpc ///< Handler can also process YAML content supplied via RPC
};
/// Handler signature for config reload - receives ConfigContext by value.
/// Handler can check ctx.supplied_yaml() for rpc-supplied content.
using ConfigReloadHandler = std::function<void(ConfigContext)>;
///
/// @brief Central registry for configuration files
///
/// Singleton that maps config keys to their handlers, supporting:
/// - YAML and legacy .config file types
/// - Multiple trigger records per config
/// - RPC reload with supplied YAML content (not for legacy .config)
/// - Runtime lookup by string key
///
/// Usage:
/// @code
/// // Register with filename record (allows runtime filename changes)
/// ConfigRegistry::Get_Instance().register_yaml(
/// "ip_allow", // key
/// "ip_allow.yaml", // default filename
/// "proxy.config.cache.ip_allow.filename", // record holding filename
/// [](ConfigContext s) { IpAllow::reconfigure(s); },
/// {"proxy.config.cache.ip_allow.filename"} // triggers
/// );
///
/// // Later, if needed, add another trigger from a different module
/// ConfigRegistry::Get_Instance().attach("ip_allow", "proxy.config.plugin.extra");
///
/// // RPC reload with supplied content:
/// // 1. Store content: registry.set_passed_config("ip_allow", yaml_node);
/// // 2. Schedule: registry.schedule_reload("ip_allow");
/// @endcode
///
class ConfigRegistry
{
public:
///
/// @brief Configuration entry
///
struct Entry {
std::string key; ///< Registry key (e.g., "ip_allow")
std::string default_filename; ///< Default filename if record not set (e.g., "ip_allow.yaml")
std::string filename_record; ///< Record containing filename (e.g., "proxy.config.cache.ip_allow.filename")
ConfigType type; ///< YAML or LEGACY - we set that based on the filename extension.
ConfigSource source{ConfigSource::FileOnly}; ///< What content sources this handler supports
ConfigReloadHandler handler; ///< Reload handler (empty for static / non-reloadable entries)
std::vector<std::string> trigger_records; ///< Records that trigger reload
bool is_required{false}; ///< Whether the file must exist on disk
std::string plugin_name; ///< Plugin that registered this entry (empty for core).
/// Resolve the actual filename (reads from record, falls back to default)
std::string resolve_filename() const;
};
///
/// @brief Get singleton instance
///
static ConfigRegistry &Get_Instance();
///
/// @brief Register a config file
///
/// Type (YAML/LEGACY) is inferred from filename extension:
/// - .yaml, .yml → YAML (supports rpc reload via ctx.supplied_yaml())
/// - others → LEGACY (file-based only)
///
/// @param key Registry key (e.g., "ip_allow")
/// @param default_filename Default filename (e.g., "ip_allow.yaml")
/// @param filename_record Record that holds the filename (e.g., "proxy.config.cache.ip_allow.filename")
/// If empty, default_filename is always used.
/// @param handler Handler that receives ConfigContext
/// @param trigger_records Records that trigger reload (optional)
/// @param is_required Whether the file must exist on disk (default false)
///
void register_config(const std::string &key, const std::string &default_filename, const std::string &filename_record,
ConfigReloadHandler handler, ConfigSource source, std::initializer_list<const char *> trigger_records = {},
bool is_required = false);
/// @brief Register a plugin-owned config file (TSCfgRegister entry point).
///
/// @a plugin_name must be non-empty; it is recorded on the Entry and used
/// for diagnostics, log attribution, and traffic_ctl status output.
/// @return @c true if the config was registered; @c false if the registration
/// was dropped (empty @a plugin_name or a colliding key). Callers that
/// surface an error to plugins (e.g. TSCfgRegister) rely on this.
bool register_plugin_config(const std::string &key, const std::string &plugin_name, const std::string &default_filename,
const std::string &filename_record, ConfigReloadHandler handler, ConfigSource source,
std::initializer_list<const char *> trigger_records = {}, bool is_required = false);
/// @brief Register a record-only config handler (no file).
///
/// Convenience method for modules that have no config file but need their
/// reload handler to participate in the config tracking system (tracing,
/// status reporting, traffic_ctl config reload).
///
/// This is NOT for arbitrary record-change callbacks - use RecRegisterConfigUpdateCb
/// for that. This is for config modules like SSLTicketKeyConfig that are reloaded
/// via record changes and need visibility in the reload infrastructure.
///
/// Internally uses ConfigSource::RecordOnly.
///
/// @param key Registry key (e.g., "ssl_ticket_key")
/// @param handler Handler that receives ConfigContext
/// @param trigger_records Records that trigger reload
///
void register_record_config(const std::string &key, ConfigReloadHandler handler,
std::initializer_list<const char *> trigger_records);
/// @brief Register a non-reloadable config file (startup files).
///
/// Static files are registered for informational purposes only - no reload
/// handler and no trigger records. This allows the registry to serve as the
/// single source of truth for all known configuration files, so that RPC
/// endpoints can gather and expose this information.
///
/// @param key Registry key (e.g., "storage")
/// @param default_filename Default filename (e.g., "storage.config")
/// @param filename_record Record holding the filename path (optional)
/// @param is_required Whether the file must exist on disk
///
void register_static_file(const std::string &key, const std::string &default_filename, const std::string &filename_record = {},
bool is_required = false);
///
/// @brief Attach a trigger record to an existing config
///
/// Can be called from any module to add additional triggers.
///
/// @note Attaches an additional record trigger to an existing config entry.
///
/// @param key The registered config key
/// @param record_name The record that triggers reload
/// @return 0 on success, -1 if key not found
///
int attach(const std::string &key, const char *record_name);
///
/// @brief Add a file dependency to an existing config
///
/// Registers an additional file with FileManager for mtime-based change detection
/// and sets up a record callback to trigger the config's reload handler when the
/// file changes on disk or the record value is modified.
///
/// This is for auxiliary/companion files that a config module depends on but that
/// are not the primary config file. For example, ip_allow depends on ip_categories.
///
///
/// @param key The registered config key (must already exist)
/// @param filename_record Record holding the filename (e.g., "proxy.config.cache.ip_categories.filename"),
/// or nullptr / empty to use @a default_filename verbatim
/// @param default_filename Default filename when record value is empty (e.g., "ip_categories.yaml")
/// @param is_required Whether the file is required to exist
/// @return 0 on success, -1 if key not found
///
int add_file_dependency(const std::string &key, const char *filename_record, const char *default_filename, bool is_required);
///
/// @brief Add a file dependency with an RPC-routable node key
///
/// Like add_file_dependency(), but also registers @p dep_key as a routable key
/// so the RPC handler can route inline YAML content to the parent entry's handler.
///
/// When an RPC reload request specifies @p dep_key, resolve() maps it to the parent
/// entry, and the content is grouped under the dep_key in the YAML node passed to
/// the handler. The handler (or its sub-modules) can then check for their key:
/// @code
/// if (auto yaml = ctx.supplied_yaml(); yaml && yaml["sni"]) {
/// // parse from yaml["sni"] instead of file
/// }
/// @endcode
///
/// @param key The registered parent config key (must already exist)
/// @param dep_key Key for RPC routing (e.g., "sni"). Must be unique across all entries and dependencies.
/// @param filename_record Record holding the filename
/// @param default_filename Default filename when record value is empty
/// @param is_required Whether the file is required to exist
/// @return 0 on success, -1 if key not found or dep_key already exists
///
int add_file_and_node_dependency(const std::string &key, const std::string &dep_key, const char *filename_record,
const char *default_filename, bool is_required);
///
/// @brief Resolve a key to its entry, handling both direct entries and dependency keys
///
/// Looks up @p key first in the direct entry map, then in the dependency key map.
/// For direct entries, returns {key, entry}. For dependency keys, returns {parent_key, parent_entry}.
/// Returns {"", nullptr} if the key is not found.
///
/// Used by the RPC handler to route inline content to the correct parent handler,
/// grouping multiple dependency keys under the same parent into a single reload.
///
/// @param key The key to look up (can be a direct entry key or a dependency key)
/// @return pair of {resolved_parent_key, entry_pointer}
///
std::pair<std::string, Entry const *> resolve(const std::string &key) const;
///
/// @brief Store passed config content for a key (internal RPC use only)
///
/// Stores YAML content passed via RPC for rpc reload.
/// Called by RPC handlers in Configuration.cc before schedule_reload().
/// Content is kept for future reference/debugging.
/// Thread-safe.
///
/// @note Not intended for external use. Will be moved to a restricted interface
/// when the plugin config registration API is introduced.
///
/// @param key The registered config key
/// @param content YAML::Node content to store
///
void set_passed_config(const std::string &key, YAML::Node content);
///
/// @brief Schedule async reload for a config key
///
/// Creates a ScheduledReloadContinuation on ET_TASK.
/// If content was set via set_passed_config(), it will be used (rpc reload).
/// Otherwise, handler reads from file.
///
/// @param key The registered config key
///
void schedule_reload(const std::string &key);
///
/// @brief Execute reload for a key (called by ScheduledReloadContinuation)
///
/// Checks for rpc-supplied content, creates context, calls handler.
/// Internal use - called from continuation on ET_TASK thread.
///
/// @param key The config key to reload
///
void execute_reload(const std::string &key);
/// look up.
bool contains(const std::string &key) const;
Entry const *find(const std::string &key) const;
/// Whether @a key names an entry that can actually be reloaded, i.e. it
/// exists and has a reload handler. Checked atomically under the registry
/// lock so callers never dereference a dangling Entry. Handler-less entries
/// (e.g. the static @c storage / @c plugin catalog entries) return @c false.
bool is_reloadable(const std::string &key) const;
/// Callback context for RecRegisterConfigUpdateCb (public for callback access)
struct TriggerContext {
std::string config_key;
Ptr<ProxyMutex> mutex;
};
private:
ConfigRegistry() = default;
~ConfigRegistry() = default;
// Non-copyable
ConfigRegistry(ConfigRegistry const &) = delete;
ConfigRegistry &operator=(ConfigRegistry const &) = delete;
/// Internal: common registration logic.
/// @return @c true if the entry was inserted, @c false on a duplicate key.
bool do_register(Entry entry);
/// Internal: setup trigger callbacks for an entry
void setup_triggers(Entry &entry);
/// Internal: wire a record callback to fire on_record_change for a config key.
/// Does NOT modify trigger_records - callers decide whether to store the record.
int wire_record_callback(const char *record_name, const std::string &config_key);
/// Internal: split the rpc-passed YAML into _reload directives and remaining
/// content, and apply both slices onto @p ctx (via the friend relationship
/// with ConfigContext). On invalid _reload (non-map), the directives are
/// dropped with a warning. @p passed_config is mutated (the "_reload" key
/// is stripped).
static void apply_passed_config(ConfigContext &ctx, YAML::Node &passed_config, std::string_view key);
/// Hash for lookup.
struct StringHash {
using is_transparent = void;
size_t
operator()(std::string_view sv) const
{
return std::hash<std::string_view>{}(sv);
}
size_t
operator()(std::string const &s) const
{
return std::hash<std::string>{}(s);
}
};
mutable std::shared_mutex _mutex;
std::unordered_map<std::string, Entry, StringHash, std::equal_to<>> _entries;
std::unordered_map<std::string, YAML::Node, StringHash, std::equal_to<>> _passed_configs;
/// Maps dependency keys to their parent entry's key.
///
/// When a coordinator entry manages multiple configuration files, each file can
/// be given a dependency key via add_file_and_node_dependency(). This allows
/// resolve() to route RPC-supplied content for a dependency key back to the
/// parent coordinator's handler, so a single reload fires for all related files.
std::unordered_map<std::string, std::string, StringHash, std::equal_to<>> _dep_key_to_parent;
};
} // namespace config