Pluggable drivers for oslo.config

Provide an interface to implement external config readers.

Change-Id: I9833f16088289147f38ea0dd03b2ba56106aa9b5
Co-Authorized-By: Emilien Macchi <emilien@redhat.com>
Implements: blueprint oslo-config-db
This commit is contained in:
Doug Hellmann 2017-04-07 17:44:27 -04:00
parent 4fffcfd4bc
commit ad1f590d96
1 changed files with 352 additions and 0 deletions

View File

@ -0,0 +1,352 @@
=================================
Backend Drivers for oslo.config
=================================
https://blueprints.launchpad.net/oslo.config/+spec/oslo-config-drivers
Currently, oslo.config is tightly bound to the plain text
configuration files. This spec describes changes to allow deployers to
store configuration data in other places, such as secret stores
managed by castellan, by adding a driver layer to oslo.config.
Problem description
===================
Various regulations and best practices say that passwords and other
secret values should not be stored in plain text in configuration
files. There are "secret store" services to manage values that should
be kept secure. Castellan provides an abstraction API for accessing
those services. Castellan also depends on oslo.config, which means
oslo.config cannot use castellan directly.
This spec describes a new driver API for oslo.config to allow us to
(separately) write a driver in castellan that can be used when it is
installed but will not cause errors when it is missing.
Proposed change
===============
oslo.config is updated so that multiple backends can be used and the
existing INI parser is turned into one driver.
As part of initializing the ``ConfigOpts`` instance and loading
settings, all known drivers will be interrogated to determine if they
can provide one or more "sources" of data. The driver is responsible
for defining its own configuration options and interpreting them to
define a data "source".
A new configuration option ``DEFAULT.config_sources`` is added to
define sources other than those provided by ``--config-file`` and
``--config-dir`` on the command line. The value for ``config_sources``
is a list of source identifiers used to find configuration settings
for other sources. Each source identifier corresponds to a
configuration option group, which provides the details for a single
source of configuration data.
When ``ConfigOpts`` looks for an option value, it goes through the
defined sources in the order they are provided, starting with the
command line, then any configuration files, and finally the sources
loaded from ``config_sources``. The first source that provides a
configured value for an option causes the search to end (sources are
not "merged" internally).
An occurance of ``--config-dir`` will continue to be treated as a
single source, as it is now (all of the options are merged into one
namespace) using the INI file driver.
Implementation details
----------------------
When ``ConfigOpts`` is done parsing the command line options and
loading configuration files, it will iterate over the items in
``DEFAULT.config_sources``. Each string in the list value will be
interpreted as the name of an option group that defines another
configuration source. The ``driver`` setting in each group will
specify which ``oslo.config`` driver to use to load a source from the
option group using ``open_source_from_opt_group()``.
::
def open_source_from_opt_group(self, conf, group_name):
"""Return an open option source.
Use group_name to find the configuration settings for the new
source then open the source and return it.
If a source cannot be open, raise an appropriate exception.
:param conf: The active configuration option handler from which
to seek configuration values.
:type conf: ConfigOpts
:param group_name: The configuration option group name where the
options for the source are stored.
:type group_name: str
:return: instance of subclass of ConfigurationSource
"""
Each ``ConfigurationSource`` instance is expected to retain any
information it needs to maintain a connection. Whether the connection
is long-lived or opened each time a configuration value is needed is
up to the driver.
The ``ConfigurationSource`` is not explicitly closed. If a connection
is lost, it is the responsibility of the driver to re-open it, or emit
a hard failure exception.
Each time ``ConfigOpts`` is asked for the value of an option, it will
iterate over the drivers and sources in the order they were registered
and call ``get()`` on the source, always passing an explicit group
name and option name.
::
def get(self, group_name, option_name, opt):
"""Return the value of the option from the group.
:param group_name: Name of the group.
:type group_name: str
:param option_name: Name of the option.
:type option_name: str
:param opt: The option definition.
:type opt: Opt
:return: Option value or NoValue.
"""
The source should return the value of the option, if it is present in
the data store being accessed. The return value should either match
the type for the ``Opt``, or another type such as a string that can be
coerced into the required type. The caller will perform the type
conversion in that case.
Converting complex types such as lists and dictionaries for storage is
left up to the driver to specify, though if the values need to be
encoded it would be good if that happened either as JSON or using the
same syntax that the INI files use.
If the value is not available in the data store, the source should
return ``oslo_config.drivers.NoValue``. We cannot use ``None`` as a
sentinel indicating a missing value because it may be a valid value or
default, so we use a custom singleton instead.
The opt parameter is provided in case the driver needs to do advanced
coercion (such as mapping an integer value to a choice). It should not
be used for type coercion except in special circumstances.
The driver is not responsible for handling deprecated option
names. The caller will look for a value using the current option name
then search again using the deprecated name(s) if no source has a
value under the current name.
New error classes with more generic names need to be derived from
``ConfigFilesNotFoundError`` and ``ConfigFilesPermissionDeniedError``
to be used in similar situations by the drivers.
All other errors raised from the drivers will be trapped by
``ConfigOpts`` and logged as warnings and the driver will be treated
as though it returned ``NoValue``.
Drivers will be implemented inside the oslo.config library, and loaded
through entry points managed by stevedore_ using the namespace
``oslo.config.driver``. This will allow us, for example, to add a
driver to the castellan library without introducing a circular
dependency between castellan and oslo.config.
.. _stevedore: https://docs.openstack.org/developer/stevedore/
Caching and Mutable Option Handling
-----------------------------------
The existing "mutate configuration" behavior, which allows a service
to tell oslo.config to reload the configuration file, is extended to
work with the new configuration sources.
Values retrieved from a ``ConfigurationSource`` may be cached by the
ConfigOpts instance to avoid repeated calls to a remote service (they
should *not* be cached by the driver). When the ``ConfigOpts`` class
is told to mutate its options, it discards any cached values it holds,
as well as any open ``ConfigurationSource`` instances. It will then
load its configuration sources again, from scratch. This avoids the
need for a cache-flushing API in the ``ConfigurationSource`` class,
keeping the drivers simple.
The existing behavior for detecting changes to options not configured
as mutable is retained, as is the existing callback system for
notifying applications that options have been reloaded.
Alternatives
------------
An earlier version of this spec focused on an etcd driver for
container use cases. That problem has been solved using a different
approach.
Other backends, such as castellan, consul, zookeeper, MySQL, and etcd,
can be implemented separately without writing additional specs, unless
implementing them will require modifying the API defined for the
drivers.
There is `another proposal <https://review.openstack.org/130047>`_
that introduces a proxy interface to configuration options. However,
it does not provide any mechanism to make it configurable.
Impact on Existing APIs
-----------------------
There are no changes to the existing public API for oslo.config.
The ``ConfigurationSource`` class and the new exceptions will be added
to the API.
The behavior when oslo.config is told to "mutate" its configuration
will change, but the call to perform the mutation is the same.
Security impact
---------------
We assume that any remote access would occur over an encrypted
connection.
Performance Impact
------------------
Because configuration options can be registered by a service at any
time during operation, it may not always be possible for a driver
initialized early in the process start up to load "all" of the
settings in one call. Therefore some configuration accesses may be
slower than when reading just from an INI file. We can use caching in
the top layer in oslo.config to mitigate this impact. Drivers are free
to implement their own optimizations internally (such as fetching all
of the keys in a namespace or all of the rows from a table), but the
ability to do so is not assumed in the driver API.
Configuration Impact
--------------------
Deployers using a secret store will need to load configuration values
into their database using a native tool. The scheme for each backend
service must be documented in order for them to be able to do this.
We may want to build a tool to read an INI file and publish it to a
remote system, but that is not part of this spec and would have to be
described separately before being implemented. Deployment tools such
as Tripleo may provide their mechanism for doing this, or contribute
to doing the work through oslo.config to be shared. It is expected
that drivers will need a ``set()`` method to support uploading
configuration settings.
Below is an example using a configuration file and hypothetical secret
store set up via the config file.
The program is started using the standard ``--config-file`` option on
the command line.
.. code-block:: console
$ app --config-file /path/to/file.conf
and the configuration file ``file.conf`` contains::
[DEFAULT]
config_sources = secret
[secret]
driver = castellan
mapping_file = /path/to/mapping.ini
Developer Impact
----------------
Developers will not notice any difference in their use of oslo.config.
Testing Impact
--------------
We will need unit tests for the priority resolution algorithm.
We will need unit tests for the driver(s).
We will need functional tests for the driver(s).
Implementation
==============
Assignee(s)
-----------
Primary assignee:
* Samuel Pilla (spilla)
Other contributors:
* Doug Hellmann (dhellmann)
Milestones
----------
queens-3 or rocky-2
Work Items
----------
* Define the base class for a configuration driver.
* Define the ``ConfigurationSource`` base class.
* Set up the namespace for entry points for drivers.
* Define a new driver for loading configuration from simple URLs to be
used as a test case.
* Extend ``ConfigOpts`` to load and use the drivers, as described
above. This will add URL handling without changing the way file
loading works.
* Update ``ConfigOpts`` to use the ``ConfigurationSource`` search
algorithm described above in addition to its current search
algorithm.
* Ensure that ``ConfigOpts`` only caches non-mutable values.
* Change ``mutate_config_files()`` to discard all of the existing data
and reload it, without performing any validation. Fix tests and
erase dead code left by rewriting that feature.
Incubation
==========
None.
Adoption
--------
TBD
Library
-------
oslo.config
Anticipated API Stabilization
-----------------------------
TBD
Documentation Impact
====================
TBD
Dependencies
============
None
References
==========
* https://etherpad.openstack.org/p/oslo.config_etcd_backend
* https://etherpad.openstack.org/p/tripleo-etcd-transition
* https://etherpad.openstack.org/p/oslo-config-pluggable-cmdb
* A `related spec`_ by Vladimir Eremin <veremin@mirantis.com>
.. _related spec: https://review.openstack.org/#/c/243114/
.. note::
This work is licensed under a `Creative Commons Attribution 3.0
Unported License
<http://creativecommons.org/licenses/by/3.0/legalcode>`_.