Decentralized configuration.
The configuration is based on oslo-config, while this module lets each module which want to be configured define the options it need by itself, and get the limited access to those config variables. Implements blueprint config-module Change-Id: Iffbb859da0cb5fb8434bc439320bf6c20f9d39b5
This commit is contained in:
parent
6864efb0cb
commit
38d72dffd0
@ -13,7 +13,16 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Import guard. No module level import during the setup procedure.
|
||||||
|
try:
|
||||||
|
__MARCONI_SETUP__
|
||||||
|
except NameError:
|
||||||
from marconi.kernel import Kernel # NOQA
|
from marconi.kernel import Kernel # NOQA
|
||||||
|
else:
|
||||||
|
import sys as _sys
|
||||||
|
_sys.stderr.write('Running from marconi source directory.\n')
|
||||||
|
del _sys
|
||||||
|
|
||||||
import marconi.version
|
import marconi.version
|
||||||
|
|
||||||
__version__ = marconi.version.version_info.deferred_version_string()
|
__version__ = marconi.version.version_info.deferred_version_string()
|
||||||
|
194
marconi/common/config.py
Normal file
194
marconi/common/config.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# Copyright (c) 2013 Rackspace, Inc.
|
||||||
|
#
|
||||||
|
# Licensed 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Decentralized configuration module.
|
||||||
|
|
||||||
|
A config variable `foo` is a read-only property accessible through
|
||||||
|
|
||||||
|
cfg.foo
|
||||||
|
|
||||||
|
, where `cfg` is either a global configuration accessible through
|
||||||
|
|
||||||
|
cfg = config.project('marconi').from_options(foo=("bar", "usage"), ...)
|
||||||
|
|
||||||
|
, or a local configuration associated with a namespace
|
||||||
|
|
||||||
|
cfg = config.namespace('drivers:transport:wsgi').from_options(port=80, ...)
|
||||||
|
|
||||||
|
The `from_options` call accepts a list of option definition, where each
|
||||||
|
option is represented as a keyword argument, in the form of either
|
||||||
|
`name=default` or `name=(default, description)`, where `name` is the
|
||||||
|
name of the option in a valid Python identifier, and `default` is the
|
||||||
|
default value of that option.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
def _init():
|
||||||
|
"""Enclose an API specific config object."""
|
||||||
|
|
||||||
|
class ConfigProxy(object):
|
||||||
|
"""Prototype of the opaque config variable accessors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Obj(dict):
|
||||||
|
__getattr__ = dict.__getitem__
|
||||||
|
__setattr__ = dict.__setitem__
|
||||||
|
|
||||||
|
conf = cfg.ConfigOpts()
|
||||||
|
my = Obj(args=[])
|
||||||
|
|
||||||
|
def namespace(name, title=None):
|
||||||
|
"""
|
||||||
|
Create a config namespace.
|
||||||
|
|
||||||
|
:param name: the section name appears in the .ini file
|
||||||
|
:param title: an optional description
|
||||||
|
:returns: the option object for the namespace
|
||||||
|
"""
|
||||||
|
|
||||||
|
grp = cfg.OptGroup(name, title)
|
||||||
|
conf.register_group(grp)
|
||||||
|
|
||||||
|
def from_options(**opts):
|
||||||
|
"""
|
||||||
|
Define options under the associated namespace.
|
||||||
|
|
||||||
|
:returns: ConfigProxy of the associated namespace
|
||||||
|
"""
|
||||||
|
|
||||||
|
for k, v in opts.items():
|
||||||
|
conf.register_opt(_make_opt(k, v), group=grp)
|
||||||
|
|
||||||
|
def from_class(cls):
|
||||||
|
grant_access_to_class(conf[grp.name], cls)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return from_class(opaque_type_of(ConfigProxy, grp.name))()
|
||||||
|
|
||||||
|
return Obj(from_options=from_options)
|
||||||
|
|
||||||
|
def project(name=None):
|
||||||
|
"""
|
||||||
|
Access the global namespace.
|
||||||
|
|
||||||
|
:param name: the name of the project
|
||||||
|
:returns: a global option object
|
||||||
|
"""
|
||||||
|
|
||||||
|
def from_options(**opts):
|
||||||
|
"""
|
||||||
|
Define options under the global namespace.
|
||||||
|
|
||||||
|
:returns: ConfigProxy of the global namespace
|
||||||
|
"""
|
||||||
|
|
||||||
|
for k, v in opts.items():
|
||||||
|
conf.register_cli_opt(_make_opt(k, v))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_cli(args):
|
||||||
|
"""
|
||||||
|
Save the CLI arguments.
|
||||||
|
|
||||||
|
:param args: a list of CLI arguments in strings
|
||||||
|
"""
|
||||||
|
|
||||||
|
my.args = []
|
||||||
|
my.args.extend(args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load(config_file=None):
|
||||||
|
"""Load the configurations from a config file.
|
||||||
|
|
||||||
|
If the file name is not supplied, look for
|
||||||
|
|
||||||
|
/etc/%project/%project.conf
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
~/.%project/%project.conf
|
||||||
|
|
||||||
|
:param config_file: the name of an alternative config file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if config_file is None:
|
||||||
|
conf(args=my.args, project=name, prog=name)
|
||||||
|
else:
|
||||||
|
conf(args=my.args, default_config_files=[config_file])
|
||||||
|
|
||||||
|
def from_class(cls):
|
||||||
|
grant_access_to_class(conf, cls)
|
||||||
|
cls.set_cli = set_cli
|
||||||
|
cls.load = load
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return from_class(opaque_type_of(ConfigProxy, name))()
|
||||||
|
|
||||||
|
return Obj(from_options=from_options)
|
||||||
|
|
||||||
|
def opaque_type_of(base, postfix):
|
||||||
|
return type('%s of %s' % (base.__name__, postfix), (base,), {})
|
||||||
|
|
||||||
|
def grant_access_to_class(pairs, cls):
|
||||||
|
for k in pairs:
|
||||||
|
# A closure is needed for each %k to let
|
||||||
|
# different properties access different %k.
|
||||||
|
def let(k=k):
|
||||||
|
setattr(cls, k, property(lambda obj: pairs[k]))
|
||||||
|
let()
|
||||||
|
|
||||||
|
return namespace, project
|
||||||
|
|
||||||
|
|
||||||
|
namespace, project = _init()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_opt(name, default):
|
||||||
|
"""
|
||||||
|
Create an oslo-config option with the type deduce from the %default
|
||||||
|
value of an option %name.
|
||||||
|
|
||||||
|
A default value of None is deduced to StrOpt; MultiStrOpt is not
|
||||||
|
supported.
|
||||||
|
|
||||||
|
:param name: the name of the option in a valid Python identifier
|
||||||
|
:param default: the default value of the option, or (default, description)
|
||||||
|
:raises: cfg.Error if the type can not be deduced.
|
||||||
|
"""
|
||||||
|
|
||||||
|
deduction = {
|
||||||
|
str: cfg.StrOpt,
|
||||||
|
bool: cfg.BoolOpt,
|
||||||
|
int: cfg.IntOpt,
|
||||||
|
long: cfg.IntOpt,
|
||||||
|
float: cfg.FloatOpt,
|
||||||
|
list: cfg.ListOpt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if type(default) is tuple:
|
||||||
|
default, help = default
|
||||||
|
else:
|
||||||
|
help = None
|
||||||
|
|
||||||
|
if default is None:
|
||||||
|
return cfg.StrOpt(name, help=help)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return deduction[type(default)](name, help=help, default=default)
|
||||||
|
except KeyError:
|
||||||
|
raise cfg.Error("unrecognized option type")
|
@ -13,12 +13,14 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import ConfigParser
|
from marconi.common import config
|
||||||
|
|
||||||
from marconi.storage import reference as storage
|
from marconi.storage import reference as storage
|
||||||
from marconi.transport.wsgi import driver as wsgi
|
from marconi.transport.wsgi import driver as wsgi
|
||||||
|
|
||||||
|
|
||||||
|
cfg = config.project('marconi').from_options()
|
||||||
|
|
||||||
|
|
||||||
class Kernel(object):
|
class Kernel(object):
|
||||||
"""
|
"""
|
||||||
Defines the Marconi Kernel
|
Defines the Marconi Kernel
|
||||||
@ -27,14 +29,13 @@ class Kernel(object):
|
|||||||
lifetimes.
|
lifetimes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config_file):
|
def __init__(self, config_file=None):
|
||||||
#TODO(kgriffs): Error handling
|
#TODO(kgriffs): Error handling
|
||||||
cfg = ConfigParser.SafeConfigParser()
|
cfg.load(config_file)
|
||||||
cfg.read(config_file)
|
|
||||||
|
|
||||||
#TODO(kgriffs): Determine driver types from cfg
|
#TODO(kgriffs): Determine driver types from cfg
|
||||||
self.storage = storage.Driver(cfg)
|
self.storage = storage.Driver()
|
||||||
self.transport = wsgi.Driver(cfg, self.storage.queue_controller,
|
self.transport = wsgi.Driver(self.storage.queue_controller,
|
||||||
self.storage.message_controller,
|
self.storage.message_controller,
|
||||||
self.storage.claim_controller)
|
self.storage.claim_controller)
|
||||||
|
|
||||||
|
@ -18,9 +18,6 @@ from marconi import storage
|
|||||||
|
|
||||||
class Driver(storage.DriverBase):
|
class Driver(storage.DriverBase):
|
||||||
|
|
||||||
def __init__(self, cfg):
|
|
||||||
self._cfg = cfg
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def queue_controller(self):
|
def queue_controller(self):
|
||||||
# TODO(kgriffs): Create base classes for controllers in common/
|
# TODO(kgriffs): Create base classes for controllers in common/
|
||||||
|
41
marconi/tests/test_config.py
Normal file
41
marconi/tests/test_config.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Copyright (c) 2013 Rackspace, Inc.
|
||||||
|
#
|
||||||
|
# Licensed 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.
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from marconi.common import config
|
||||||
|
from marconi.tests.util import suite
|
||||||
|
|
||||||
|
|
||||||
|
cfg = config.project().from_options(
|
||||||
|
without_help=3,
|
||||||
|
with_help=(None, "nonsense"))
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(suite.TestSuite):
|
||||||
|
|
||||||
|
def test_cli(self):
|
||||||
|
args = ['--with_help', 'sense']
|
||||||
|
cfg.set_cli(args)
|
||||||
|
cfg.load(self.conf_path('wsgi_reference.conf'))
|
||||||
|
self.assertEquals(cfg.with_help, 'sense')
|
||||||
|
cfg.set_cli([])
|
||||||
|
cfg.load()
|
||||||
|
self.assertEquals(cfg.with_help, None)
|
||||||
|
|
||||||
|
def test_wrong_type(self):
|
||||||
|
ns = config.namespace('local')
|
||||||
|
with testtools.ExpectedException(config.cfg.Error):
|
||||||
|
ns.from_options(opt={})
|
@ -13,16 +13,18 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from marconi.common import config
|
||||||
from marconi import transport
|
from marconi import transport
|
||||||
|
|
||||||
|
|
||||||
|
cfg = config.namespace('drivers:transport:wsgi').from_options(port=8888)
|
||||||
|
|
||||||
|
|
||||||
class Driver(transport.DriverBase):
|
class Driver(transport.DriverBase):
|
||||||
|
|
||||||
def __init__(self, cfg, queue_controller, message_controller,
|
def __init__(self, queue_controller, message_controller,
|
||||||
claim_controller):
|
claim_controller):
|
||||||
|
|
||||||
self._cfg = cfg
|
|
||||||
|
|
||||||
# E.g.:
|
# E.g.:
|
||||||
#
|
#
|
||||||
# self._queue_controller.create(tenant_id, queue_name)
|
# self._queue_controller.create(tenant_id, queue_name)
|
||||||
|
3
setup.py
3
setup.py
@ -14,8 +14,11 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
|
import __builtin__
|
||||||
|
__builtin__.__MARCONI_SETUP__ = None
|
||||||
from marconi.openstack.common import setup as common_setup
|
from marconi.openstack.common import setup as common_setup
|
||||||
|
|
||||||
requires = common_setup.parse_requirements()
|
requires = common_setup.parse_requirements()
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
cliff
|
cliff
|
||||||
http://tarballs.openstack.org/oslo-config/oslo.config-1.1.0b1.tar.gz#egg=oslo.config
|
oslo.config>=1.1.0
|
||||||
|
Loading…
Reference in New Issue
Block a user