summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-07-05 19:33:57 +0000
committerGerrit Code Review <review@openstack.org>2014-07-05 19:33:57 +0000
commit28ef50e9311166b23fabdef0cbfdba49f257ac2d (patch)
treeaa294b84408b8cd736676048ae97a99568d0e82f
parent3d3ca2ac3f9a59a3d1156bdb8f4f797136821d9d (diff)
parentbcb8b7b8f61bd43e7d314c6d743c436501f1728f (diff)
downloadoslo-config-28ef50e9311166b23fabdef0cbfdba49f257ac2d.tar.gz
Merge "Add cfgfilter.ConfigFilter"
-rw-r--r--doc/source/cfgfilter.rst5
-rw-r--r--doc/source/index.rst1
-rw-r--r--oslo/config/cfgfilter.py318
-rw-r--r--tests/test_cfgfilter.py280
-rw-r--r--tests/testmods/fbaar_baa_opt.py21
-rw-r--r--tests/testmods/fbar_foo_opt.py21
-rw-r--r--tests/testmods/fblaa_opt.py21
7 files changed, 667 insertions, 0 deletions
diff --git a/doc/source/cfgfilter.rst b/doc/source/cfgfilter.rst
new file mode 100644
index 0000000..a471f6e
--- /dev/null
+++ b/doc/source/cfgfilter.rst
@@ -0,0 +1,5 @@
+--------------------
+The cfgfilter Module
+--------------------
+
+.. automodule:: oslo.config.cfgfilter
diff --git a/doc/source/index.rst b/doc/source/index.rst
index b41fb81..edfc4fb 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -14,6 +14,7 @@ Contents
opts
types
configopts
+ cfgfilter
helpers
parser
exceptions
diff --git a/oslo/config/cfgfilter.py b/oslo/config/cfgfilter.py
new file mode 100644
index 0000000..e14363f
--- /dev/null
+++ b/oslo/config/cfgfilter.py
@@ -0,0 +1,318 @@
+# Copyright 2014 Red Hat, 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.
+
+r"""
+There are two use cases for the ConfigFilter class:
+
+1. Help enforce that a given module does not access options registered
+ by another module, without first declaring those cross-module
+ dependencies using import_opt().
+
+2. Prevent private configuration opts from being visible to modules
+ other than the one which registered it.
+
+Cross-Module Option Dependencies
+--------------------------------
+
+When using the global cfg.CONF object, it is quite common for a module
+to require the existence of configuration options registered by other
+modules.
+
+For example, if module 'foo' registers the 'blaa' option and the module
+'bar' uses the 'blaa' option then 'bar' might do::
+
+ import foo
+
+ print(CONF.blaa)
+
+However, it's completely non-obvious why foo is being imported (is it
+unused, can we remove the import) and where the 'blaa' option comes from.
+
+The CONF.import_opt() method allows such a dependency to be explicitly
+declared::
+
+ CONF.import_opt('blaa', 'foo')
+ print(CONF.blaa)
+
+However, import_opt() has a weakness - if 'bar' imports 'foo' using the
+import builtin and doesn't use import_opt() to import 'blaa', then 'blaa'
+can still be used without problems. Similarly, where multiple options
+are registered a module imported via importopt(), a lazy programmer can
+get away with only declaring a dependency on a single option.
+
+The ConfigFilter class provides a way to ensure that options are not
+available unless they have been registered in the module or imported using
+import_opt() e.g. with::
+
+ CONF = ConfigFilter(cfg.CONF)
+ CONF.import_opt('blaa', 'foo')
+ print(CONF.blaa)
+
+no other options other than 'blaa' are available via CONF.
+
+Private Configuration Options
+-----------------------------
+
+Libraries which register configuration options typically do not want
+users of the library API to access those configuration options. If
+API users do access private configuration options, those users will
+be disrupted if and when a configuration option is renamed. In other
+words, one does not typically wish for the name of the private config
+options to be part of the public API.
+
+The ConfigFilter class provides a way for a library to register
+options such that they are not visible via the ConfigOpts instance
+which the API user supplies to the library. For example::
+
+ from __future__ import print_function
+
+ from oslo.config.cfg import *
+ from oslo.config.cfgfilter import *
+
+ class Widget(object):
+
+ def __init__(self, conf):
+ self.conf = conf
+ self._private_conf = ConfigFilter(self.conf)
+ self._private_conf.register_opt(StrOpt('foo'))
+
+ @property
+ def foo(self):
+ return self._private_conf.foo
+
+ conf = ConfigOpts()
+ widget = Widget(conf)
+ print(widget.foo)
+ print(conf.foo) # raises NoSuchOptError
+
+"""
+
+import collections
+import itertools
+
+from oslo.config import cfg
+
+
+class ConfigFilter(collections.Mapping):
+
+ """A helper class which wraps a ConfigOpts object.
+
+ ConfigFilter enforces the explicit declaration of dependencies on external
+ options and allows private options which are not registered with the
+ wrapped Configopts object.
+ """
+
+ def __init__(self, conf):
+ """Construct a ConfigFilter object.
+
+ :param conf: a ConfigOpts object
+ """
+ self._conf = conf
+ self._fconf = cfg.ConfigOpts()
+ self._sync()
+
+ self._imported_opts = set()
+ self._imported_groups = dict()
+
+ def _sync(self):
+ if self._fconf._namespace is not self._conf._namespace:
+ self._fconf.clear()
+ self._fconf._namespace = self._conf._namespace
+ self._fconf._args = self._conf._args
+
+ def __getattr__(self, name):
+ """Look up an option value.
+
+ :param name: the opt name (or 'dest', more precisely)
+ :returns: the option value (after string subsititution) or a GroupAttr
+ :raises: NoSuchOptError,ConfigFileValueError,TemplateSubstitutionError
+ """
+ if name in self._imported_groups:
+ return self._imported_groups[name]
+ elif name in self._imported_opts:
+ return getattr(self._conf, name)
+ else:
+ self._sync()
+ return getattr(self._fconf, name)
+
+ def __getitem__(self, key):
+ """Look up an option value."""
+ return getattr(self, key)
+
+ def __contains__(self, key):
+ """Return True if key is the name of a registered opt or group."""
+ return (key in self._fconf or
+ key in self._imported_opts or
+ key in self._imported_groups)
+
+ def __iter__(self):
+ """Iterate over all registered opt and group names."""
+ return itertools.chain(self._fconf.keys(),
+ self._imported_opts,
+ self._imported_groups.keys())
+
+ def __len__(self):
+ """Return the number of options and option groups."""
+ return (len(self._fconf) +
+ len(self._imported_opts) +
+ len(self._imported_groups))
+
+ @staticmethod
+ def _already_registered(conf, opt, group=None):
+ group_name = group.name if isinstance(group, cfg.OptGroup) else group
+ return ((group_name is None and
+ opt.dest in conf) or
+ (group_name is not None and
+ group_name in conf and
+ opt.dest in conf[group_name]))
+
+ def register_opt(self, opt, group=None):
+ """Register an option schema.
+
+ :param opt: an instance of an Opt sub-class
+ :param group: an optional OptGroup object or group name
+ :return: False if the opt was already registered, True otherwise
+ :raises: DuplicateOptError
+ """
+ if self._already_registered(self._conf, opt, group):
+ # Raises DuplicateError if there is another opt with the same name
+ ret = self._conf.register_opt(opt, group)
+ self._import_opt(opt.dest, group)
+ return ret
+ else:
+ return self._fconf.register_opt(opt, group)
+
+ def register_opts(self, opts, group=None):
+ """Register multiple option schemas at once."""
+ for opt in opts:
+ self.register_opt(opt, group)
+
+ def register_cli_opt(self, opt, group=None):
+ """Register a CLI option schema.
+
+ :param opt: an instance of an Opt sub-class
+ :param group: an optional OptGroup object or group name
+ :return: False if the opt was already register, True otherwise
+ :raises: DuplicateOptError, ArgsAlreadyParsedError
+ """
+ if self._already_registered(self._conf, opt, group):
+ # Raises DuplicateError if there is another opt with the same name
+ ret = self._conf.register_cli_opt(opt, group)
+ self._import_opt(opt.dest, group)
+ return ret
+ else:
+ return self._fconf.register_cli_opt(opt, group)
+
+ def register_cli_opts(self, opts, group=None):
+ """Register multiple CLI option schemas at once."""
+ for opt in opts:
+ self.register_cli_opt(opt, group)
+
+ def register_group(self, group):
+ """Register an option group.
+
+ :param group: an OptGroup object
+ """
+ self._fconf.register_group(group)
+
+ def import_opt(self, opt_name, module_str, group=None):
+ """Import an option definition from a module.
+
+ :param name: the name/dest of the opt
+ :param module_str: the name of a module to import
+ :param group: an option OptGroup object or group name
+ :raises: NoSuchOptError, NoSuchGroupError
+ """
+ self._conf.import_opt(opt_name, module_str, group)
+ self._import_opt(opt_name, group)
+
+ def import_group(self, group, module_str):
+ """Import an option group from a module.
+
+ Note that this allows access to all options registered with
+ the group whether or not those options were registered by
+ the given module.
+
+ :param group: an option OptGroup object or group name
+ :param module_str: the name of a module to import
+ :raises: ImportError, NoSuchGroupError
+ """
+ self._conf.import_group(group, module_str)
+ group = self._import_group(group)
+ group._all_opts = True
+
+ def _import_opt(self, opt_name, group):
+ if group is None:
+ self._imported_opts.add(opt_name)
+ return True
+ else:
+ group = self._import_group(group)
+ return group._import_opt(opt_name)
+
+ def _import_group(self, group_or_name):
+ if isinstance(group_or_name, cfg.OptGroup):
+ group_name = group_or_name.name
+ else:
+ group_name = group_or_name
+
+ if group_name in self._imported_groups:
+ return self._imported_groups[group_name]
+ else:
+ group = self.GroupAttr(self._conf, group_name)
+ self._imported_groups[group_name] = group
+ return group
+
+ class GroupAttr(collections.Mapping):
+
+ """Helper class to wrap a group object.
+
+ Represents the option values of a group as a mapping and attributes.
+ """
+
+ def __init__(self, conf, group):
+ """Construct a GroupAttr object.
+
+ :param conf: a ConfigOpts object
+ :param group: an OptGroup object
+ """
+ self._conf = conf
+ self._group = group
+ self._imported_opts = set()
+ self._all_opts = False
+
+ def __getattr__(self, name):
+ """Look up an option value."""
+ if not self._all_opts and name not in self._imported_opts:
+ raise cfg.NoSuchOptError(name)
+ return getattr(self._conf[self._group], name)
+
+ def __getitem__(self, key):
+ """Look up an option value."""
+ return getattr(self, key)
+
+ def __contains__(self, key):
+ """Return True if key is the name of a registered opt or group."""
+ return key in self._imported_opts
+
+ def __iter__(self):
+ """Iterate over all registered opt and group names."""
+ for key in self._imported_opts:
+ yield key
+
+ def __len__(self):
+ """Return the number of options and option groups."""
+ return len(self._imported_opts)
+
+ def _import_opt(self, opt_name):
+ self._imported_opts.add(opt_name)
diff --git a/tests/test_cfgfilter.py b/tests/test_cfgfilter.py
new file mode 100644
index 0000000..b2ec902
--- /dev/null
+++ b/tests/test_cfgfilter.py
@@ -0,0 +1,280 @@
+# Copyright 2014 Red Hat, 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.
+
+from oslotest import base as test_base
+
+from oslo.config import cfg
+from oslo.config import cfgfilter
+
+
+class BaseTestCase(test_base.BaseTestCase):
+
+ def setUp(self, conf=None):
+ super(BaseTestCase, self).setUp()
+ if conf is None:
+ self.conf = cfg.ConfigOpts()
+ else:
+ self.conf = conf
+ self.fconf = cfgfilter.ConfigFilter(self.conf)
+
+
+class RegisterTestCase(BaseTestCase):
+
+ def test_register_opt_default(self):
+ self.fconf.register_opt(cfg.StrOpt('foo', default='bar'))
+
+ self.assertEqual('bar', self.fconf.foo)
+ self.assertEqual('bar', self.fconf['foo'])
+ self.assertIn('foo', self.fconf)
+ self.assertEqual(['foo'], list(self.fconf))
+ self.assertEqual(1, len(self.fconf))
+
+ self.assertNotIn('foo', self.conf)
+ self.assertEqual(0, len(self.conf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
+
+ def test_register_opt_none_default(self):
+ self.fconf.register_opt(cfg.StrOpt('foo'))
+
+ self.assertIsNone(self.fconf.foo)
+ self.assertIsNone(self.fconf['foo'])
+ self.assertIn('foo', self.fconf)
+ self.assertEqual(['foo'], list(self.fconf))
+ self.assertEqual(1, len(self.fconf))
+
+ self.assertNotIn('foo', self.conf)
+ self.assertEqual(0, len(self.conf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'foo')
+
+ def test_register_grouped_opt_default(self):
+ self.fconf.register_opt(cfg.StrOpt('foo', default='bar'),
+ group='blaa')
+
+ self.assertEqual('bar', self.fconf.blaa.foo)
+ self.assertEqual('bar', self.fconf['blaa']['foo'])
+ self.assertIn('blaa', self.fconf)
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertEqual(['blaa'], list(self.fconf))
+ self.assertEqual(['foo'], list(self.fconf.blaa))
+ self.assertEqual(1, len(self.fconf))
+ self.assertEqual(1, len(self.fconf.blaa))
+
+ self.assertNotIn('blaa', self.conf)
+ self.assertEqual(0, len(self.conf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
+
+ def test_register_grouped_opt_none_default(self):
+ self.fconf.register_opt(cfg.StrOpt('foo'), group='blaa')
+
+ self.assertIsNone(self.fconf.blaa.foo)
+ self.assertIsNone(self.fconf['blaa']['foo'])
+ self.assertIn('blaa', self.fconf)
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertEqual(['blaa'], list(self.fconf))
+ self.assertEqual(['foo'], list(self.fconf.blaa))
+ self.assertEqual(1, len(self.fconf))
+ self.assertEqual(1, len(self.fconf.blaa))
+
+ self.assertNotIn('blaa', self.conf)
+ self.assertEqual(0, len(self.conf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
+
+ def test_register_group(self):
+ group = cfg.OptGroup('blaa')
+ self.fconf.register_group(group)
+ self.fconf.register_opt(cfg.StrOpt('foo'), group=group)
+
+ self.assertIsNone(self.fconf.blaa.foo)
+ self.assertIsNone(self.fconf['blaa']['foo'])
+ self.assertIn('blaa', self.fconf)
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertEqual(['blaa'], list(self.fconf))
+ self.assertEqual(['foo'], list(self.fconf.blaa))
+ self.assertEqual(1, len(self.fconf))
+ self.assertEqual(1, len(self.fconf.blaa))
+
+ self.assertNotIn('blaa', self.conf)
+ self.assertEqual(0, len(self.conf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.conf, 'blaa')
+
+ def test_register_opts(self):
+ self.fconf.register_opts([cfg.StrOpt('foo'),
+ cfg.StrOpt('bar')])
+ self.assertIn('foo', self.fconf)
+ self.assertIn('bar', self.fconf)
+ self.assertNotIn('foo', self.conf)
+ self.assertNotIn('bar', self.conf)
+
+ def test_register_cli_opt(self):
+ self.fconf.register_cli_opt(cfg.StrOpt('foo'))
+ self.assertIn('foo', self.fconf)
+ self.assertNotIn('foo', self.conf)
+
+ def test_register_cli_opts(self):
+ self.fconf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')])
+ self.assertIn('foo', self.fconf)
+ self.assertIn('bar', self.fconf)
+ self.assertNotIn('foo', self.conf)
+ self.assertNotIn('bar', self.conf)
+
+ def test_register_opts_grouped(self):
+ self.fconf.register_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')],
+ group='blaa')
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertIn('bar', self.fconf.blaa)
+ self.assertNotIn('blaa', self.conf)
+
+ def test_register_cli_opt_grouped(self):
+ self.fconf.register_cli_opt(cfg.StrOpt('foo'), group='blaa')
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertNotIn('blaa', self.conf)
+
+ def test_register_cli_opts_grouped(self):
+ self.fconf.register_cli_opts([cfg.StrOpt('foo'), cfg.StrOpt('bar')],
+ group='blaa')
+ self.assertIn('foo', self.fconf.blaa)
+ self.assertIn('bar', self.fconf.blaa)
+ self.assertNotIn('blaa', self.conf)
+
+ def test_unknown_opt(self):
+ self.assertNotIn('foo', self.fconf)
+ self.assertEqual(0, len(self.fconf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
+ self.assertNotIn('blaa', self.conf)
+
+ def test_blocked_opt(self):
+ self.conf.register_opt(cfg.StrOpt('foo'))
+
+ self.assertIn('foo', self.conf)
+ self.assertEqual(1, len(self.conf))
+ self.assertIsNone(self.conf.foo)
+ self.assertNotIn('foo', self.fconf)
+ self.assertEqual(0, len(self.fconf))
+ self.assertRaises(cfg.NoSuchOptError, getattr, self.fconf, 'foo')
+
+ def test_already_registered_opt(self):
+ self.conf.register_opt(cfg.StrOpt('foo'))
+ self.fconf.register_opt(cfg.StrOpt('foo'))
+
+ self.assertIn('foo', self.conf)
+ self.assertEqual(1, len(self.conf))
+ self.assertIsNone(self.conf.foo)
+ self.assertIn('foo', self.fconf)
+ self.assertEqual(1, len(self.fconf))
+ self.assertIsNone(self.fconf.foo)
+
+ self.conf.set_override('foo', 'bar')
+
+ self.assertEqual('bar', self.conf.foo)
+ self.assertEqual('bar', self.fconf.foo)
+
+ def test_already_registered_opts(self):
+ self.conf.register_opts([cfg.StrOpt('foo'),
+ cfg.StrOpt('fu')])
+ self.fconf.register_opts([cfg.StrOpt('foo'),
+ cfg.StrOpt('bu')])
+
+ self.assertIn('foo', self.conf)
+ self.assertIn('fu', self.conf)
+ self.assertNotIn('bu', self.conf)
+ self.assertEqual(2, len(self.conf))
+ self.assertIsNone(self.conf.foo)
+ self.assertIsNone(self.conf.fu)
+ self.assertIn('foo', self.fconf)
+ self.assertIn('bu', self.fconf)
+ self.assertNotIn('fu', self.fconf)
+ self.assertEqual(2, len(self.fconf))
+ self.assertIsNone(self.fconf.foo)
+ self.assertIsNone(self.fconf.bu)
+
+ self.conf.set_override('foo', 'bar')
+
+ self.assertEqual('bar', self.conf.foo)
+ self.assertEqual('bar', self.fconf.foo)
+
+ def test_already_registered_cli_opt(self):
+ self.conf.register_cli_opt(cfg.StrOpt('foo'))
+ self.fconf.register_cli_opt(cfg.StrOpt('foo'))
+
+ self.assertIn('foo', self.conf)
+ self.assertEqual(1, len(self.conf))
+ self.assertIsNone(self.conf.foo)
+ self.assertIn('foo', self.fconf)
+ self.assertEqual(1, len(self.fconf))
+ self.assertIsNone(self.fconf.foo)
+
+ self.conf.set_override('foo', 'bar')
+
+ self.assertEqual('bar', self.conf.foo)
+ self.assertEqual('bar', self.fconf.foo)
+
+ def test_already_registered_cli_opts(self):
+ self.conf.register_cli_opts([cfg.StrOpt('foo'),
+ cfg.StrOpt('fu')])
+ self.fconf.register_cli_opts([cfg.StrOpt('foo'),
+ cfg.StrOpt('bu')])
+
+ self.assertIn('foo', self.conf)
+ self.assertIn('fu', self.conf)
+ self.assertNotIn('bu', self.conf)
+ self.assertEqual(2, len(self.conf))
+ self.assertIsNone(self.conf.foo)
+ self.assertIsNone(self.conf.fu)
+ self.assertIn('foo', self.fconf)
+ self.assertIn('bu', self.fconf)
+ self.assertNotIn('fu', self.fconf)
+ self.assertEqual(2, len(self.fconf))
+ self.assertIsNone(self.fconf.foo)
+ self.assertIsNone(self.fconf.bu)
+
+ self.conf.set_override('foo', 'bar')
+
+ self.assertEqual('bar', self.conf.foo)
+ self.assertEqual('bar', self.fconf.foo)
+
+
+class ImportTestCase(BaseTestCase):
+
+ def setUp(self):
+ super(ImportTestCase, self).setUp(cfg.CONF)
+
+ def test_import_opt(self):
+ self.assertFalse(hasattr(self.conf, 'fblaa'))
+ self.conf.import_opt('fblaa', 'tests.testmods.fblaa_opt')
+ self.assertTrue(hasattr(self.conf, 'fblaa'))
+ self.assertFalse(hasattr(self.fconf, 'fblaa'))
+ self.fconf.import_opt('fblaa', 'tests.testmods.fblaa_opt')
+ self.assertTrue(hasattr(self.fconf, 'fblaa'))
+
+ def test_import_opt_in_group(self):
+ self.assertFalse(hasattr(self.conf, 'fbar'))
+ self.conf.import_opt('foo', 'tests.testmods.fbar_foo_opt',
+ group='fbar')
+ self.assertTrue(hasattr(self.conf, 'fbar'))
+ self.assertTrue(hasattr(self.conf.fbar, 'foo'))
+ self.assertFalse(hasattr(self.fconf, 'fbar'))
+ self.fconf.import_opt('foo', 'tests.testmods.fbar_foo_opt',
+ group='fbar')
+ self.assertTrue(hasattr(self.fconf, 'fbar'))
+ self.assertTrue(hasattr(self.fconf.fbar, 'foo'))
+
+ def test_import_group(self):
+ self.assertFalse(hasattr(self.conf, 'fbaar'))
+ self.conf.import_group('fbaar', 'tests.testmods.fbaar_baa_opt')
+ self.assertTrue(hasattr(self.conf, 'fbaar'))
+ self.assertTrue(hasattr(self.conf.fbaar, 'baa'))
+ self.assertFalse(hasattr(self.fconf, 'fbaar'))
+ self.fconf.import_group('fbaar', 'tests.testmods.fbaar_baa_opt')
+ self.assertTrue(hasattr(self.fconf, 'fbaar'))
+ self.assertTrue(hasattr(self.fconf.fbaar, 'baa'))
diff --git a/tests/testmods/fbaar_baa_opt.py b/tests/testmods/fbaar_baa_opt.py
new file mode 100644
index 0000000..68875b1
--- /dev/null
+++ b/tests/testmods/fbaar_baa_opt.py
@@ -0,0 +1,21 @@
+# Copyright 2012 Red Hat, 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.
+
+from oslo.config import cfg
+
+CONF = cfg.CONF
+
+opt = cfg.StrOpt('baa')
+
+CONF.register_opt(opt, group='fbaar')
diff --git a/tests/testmods/fbar_foo_opt.py b/tests/testmods/fbar_foo_opt.py
new file mode 100644
index 0000000..c0280a4
--- /dev/null
+++ b/tests/testmods/fbar_foo_opt.py
@@ -0,0 +1,21 @@
+# Copyright 2013 Red Hat, 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.
+
+from oslo.config import cfg
+
+CONF = cfg.CONF
+
+opt = cfg.StrOpt('foo')
+
+CONF.register_opt(opt, group='fbar')
diff --git a/tests/testmods/fblaa_opt.py b/tests/testmods/fblaa_opt.py
new file mode 100644
index 0000000..8b5258d
--- /dev/null
+++ b/tests/testmods/fblaa_opt.py
@@ -0,0 +1,21 @@
+# Copyright 2012 Red Hat, 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.
+
+from oslo.config import cfg
+
+CONF = cfg.CONF
+
+opt = cfg.StrOpt('fblaa')
+
+CONF.register_opt(opt)