diff options
-rw-r--r-- | oslo/config/cfg.py | 30 | ||||
-rw-r--r-- | oslo/config/fixture.py | 32 | ||||
-rw-r--r-- | oslo/config/types.py | 50 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | tests/test_cfg.py | 20 | ||||
-rw-r--r-- | tests/test_fixture.py | 25 | ||||
-rw-r--r-- | tests/test_types.py | 33 |
7 files changed, 177 insertions, 14 deletions
diff --git a/oslo/config/cfg.py b/oslo/config/cfg.py index 98507b4..8c6d749 100644 --- a/oslo/config/cfg.py +++ b/oslo/config/cfg.py @@ -714,7 +714,7 @@ class Opt(object): :param short: the short opt name :param kwargs: the keyword arguments for add_argument() :param prefix: an optional prefix to prepend to the opt name - :param position: whether the optional is a positional CLI argument + :param positional: whether the option is a positional CLI argument """ def hyphen(arg): return arg if not positional else '' @@ -950,6 +950,15 @@ class DictOpt(Opt): super(DictOpt, self).__init__(name, type=types.Dict(), **kwargs) +class IPOpt(Opt): + + """Opt with IPAddress type (either IPv4, IPv6 or both).""" + + def __init__(self, name, version=None, **kwargs): + super(IPOpt, self).__init__(name, type=types.IPAddress(version), + **kwargs) + + class MultiOpt(Opt): """Multi-value option. @@ -1447,15 +1456,14 @@ class _Namespace(argparse.Namespace): """ for group_name, name in names: name = name if group_name is None else group_name + '_' + name - try: - value = getattr(self, name) - if value is not None: - # argparse ignores default=None for nargs='*' - if positional and not value: - value = self.default - return value - except AttributeError: - pass + value = getattr(self, name, None) + if value is not None: + # argparse ignores default=None for nargs='*' and returns [] + if positional and not value: + continue + + return value + raise KeyError def _get_value(self, names, multi, positional): @@ -1927,7 +1935,7 @@ class ConfigOpts(collections.Mapping): searched by the module level find_config_files() function is used. The first matching file is returned. - :param basename: the filename, e.g. 'policy.json' + :param name: the filename, e.g. 'policy.json' :returns: the path to a matching file, or None """ dirs = [] diff --git a/oslo/config/fixture.py b/oslo/config/fixture.py index fd74f3b..2bb6cf8 100644 --- a/oslo/config/fixture.py +++ b/oslo/config/fixture.py @@ -84,3 +84,35 @@ class Config(fixtures.Fixture): """ for opt in opts: self.register_opt(opt, group=group) + + def register_cli_opt(self, opt, group=None): + """Register a single CLI option for the test run. + + Options registered in this manner will automatically be unregistered + during cleanup. + + If a `group` argument is supplied, it will register the new option + to that group, otherwise the option is registered to the ``default`` + group. + + CLI options must be registered before the command line and config files + are parsed. This is to ensure that all CLI options are shown in --help + and option validation works as expected. + """ + self.conf.register_cli_opt(opt, group=group) + self._registered_config_opts.setdefault(group, set()).add(opt) + + def register_cli_opts(self, opts, group=None): + """Register multiple CLI options for the test run. + + This works in the same manner as register_opt() but takes a list of + options as the first argument. All arguments will be registered to the + same group if the ``group`` argument is supplied, otherwise all options + will be registered to the ``default`` group. + + CLI options must be registered before the command line and config files + are parsed. This is to ensure that all CLI options are shown in --help + and option validation works as expected. + """ + for opt in opts: + self.register_cli_opt(opt, group=group) diff --git a/oslo/config/types.py b/oslo/config/types.py index a411942..541095b 100644 --- a/oslo/config/types.py +++ b/oslo/config/types.py @@ -18,6 +18,7 @@ Use these classes as values for the `type` argument to :class:`oslo.config.cfg.Opt` and its subclasses. """ +import netaddr class String(object): @@ -332,3 +333,52 @@ class Dict(object): (self.__class__ == other.__class__) and (self.value_type == other.value_type) ) + + +class IPAddress(object): + + """IP address type + + Represents either ipv4 or ipv6. Without specifying version parameter both + versions are checked + + :param version: defines which version should be explicitly checked (4 or 6) + + """ + + def __init__(self, version=None): + version_checkers = { + None: self._check_both_versions, + 4: self._check_ipv4, + 6: self._check_ipv6 + } + + self.version_checker = version_checkers.get(version) + if self.version_checker is None: + raise TypeError("%s is not a valid IP version." % version) + + def __call__(self, value): + value = str(value) + if not value: + raise ValueError("IP address cannot be an empty string") + self.version_checker(value) + return value + + def __repr__(self): + return "IPAddress" + + def __eq__(self, other): + return self.__class__ == other.__class__ + + def _check_ipv4(self, address): + if not netaddr.valid_ipv4(address, netaddr.core.INET_PTON): + raise ValueError("%s is not an IPv4 address" % address) + + def _check_ipv6(self, address): + if not netaddr.valid_ipv6(address, netaddr.core.INET_PTON): + raise ValueError("%s is not an IPv6 address" % address) + + def _check_both_versions(self, address): + if not (netaddr.valid_ipv4(address, netaddr.core.INET_PTON) or + netaddr.valid_ipv6(address, netaddr.core.INET_PTON)): + raise ValueError("%s is not IPv4 or IPv6 address" % address) diff --git a/requirements.txt b/requirements.txt index 9c5b495..cb67f29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ argparse +netaddr>=0.7.6 six>=1.7.0 stevedore>=0.14 diff --git a/tests/test_cfg.py b/tests/test_cfg.py index 165ae31..268565f 100644 --- a/tests/test_cfg.py +++ b/tests/test_cfg.py @@ -14,6 +14,7 @@ import argparse import errno +import functools import os import shutil import sys @@ -265,6 +266,9 @@ class CliOptsTestCase(BaseTestCase): deps - a tuple of deprecated name/group """ + IPv4Opt = functools.partial(cfg.IPOpt, version=4) + IPv6Opt = functools.partial(cfg.IPOpt, version=6) + scenarios = [ ('str_default', dict(opt_class=cfg.StrOpt, default=None, cli_args=[], value=None, @@ -364,6 +368,22 @@ class CliOptsTestCase(BaseTestCase): ('float_arg_deprecated_group_and_name', dict(opt_class=cfg.FloatOpt, default=None, cli_args=['--old-oof', '2.0'], value=2.0, deps=('oof', 'old'))), + ('ipv4addr_arg', + dict(opt_class=IPv4Opt, default=None, + cli_args=['--foo', '192.168.0.1'], value='192.168.0.1', + deps=(None, None))), + ('ipaddr_arg_implicitv4', + dict(opt_class=cfg.IPOpt, default=None, + cli_args=['--foo', '192.168.0.1'], value='192.168.0.1', + deps=(None, None))), + ('ipaddr_arg_implicitv6', + dict(opt_class=cfg.IPOpt, default=None, + cli_args=['--foo', 'abcd:ef::1'], value='abcd:ef::1', + deps=(None, None))), + ('ipv6addr_arg', + dict(opt_class=IPv6Opt, default=None, + cli_args=['--foo', 'abcd:ef::1'], value='abcd:ef::1', + deps=(None, None))), ('list_default', dict(opt_class=cfg.ListOpt, default=['bar'], cli_args=[], value=['bar'], deps=(None, None))), diff --git a/tests/test_fixture.py b/tests/test_fixture.py index 34894b6..def61c1 100644 --- a/tests/test_fixture.py +++ b/tests/test_fixture.py @@ -28,8 +28,6 @@ class ConfigTestCase(base.BaseTestCase): super(ConfigTestCase, self).setUp() self.config_fixture = self.useFixture(config.Config(conf)) self.config = self.config_fixture.config - self.register_opt = self.config_fixture.register_opt - self.register_opts = self.config_fixture.register_opts self.config_fixture.register_opt(cfg.StrOpt( 'testing_option', default='initial_value')) @@ -55,7 +53,7 @@ class ConfigTestCase(base.BaseTestCase): def test_register_options(self): opt1 = cfg.StrOpt('first_test_opt', default='initial_value_1') opt2 = cfg.StrOpt('second_test_opt', default='initial_value_2') - self.register_opts([opt1, opt2]) + self.config_fixture.register_opts([opt1, opt2]) self.assertEqual(conf.get('first_test_opt'), opt1.default) self.assertEqual(conf.get('second_test_opt'), opt2.default) @@ -66,3 +64,24 @@ class ConfigTestCase(base.BaseTestCase): opt.default) self.config_fixture.cleanUp() self.assertRaises(cfg.NoSuchOptError, conf.get, 'new_test_opt') + + def test_register_cli_option(self): + opt = cfg.StrOpt('new_test_opt', default='initial_value') + self.config_fixture.register_cli_opt(opt) + self.assertEqual(conf.get('new_test_opt'), + opt.default) + + def test_register_cli_options(self): + opt1 = cfg.StrOpt('first_test_opt', default='initial_value_1') + opt2 = cfg.StrOpt('second_test_opt', default='initial_value_2') + self.config_fixture.register_cli_opts([opt1, opt2]) + self.assertEqual(conf.get('first_test_opt'), opt1.default) + self.assertEqual(conf.get('second_test_opt'), opt2.default) + + def test_cleanup_unregister_cli_option(self): + opt = cfg.StrOpt('new_test_opt', default='initial_value') + self.config_fixture.register_cli_opt(opt) + self.assertEqual(conf.get('new_test_opt'), + opt.default) + self.config_fixture.cleanUp() + self.assertRaises(cfg.NoSuchOptError, conf.get, 'new_test_opt') diff --git a/tests/test_types.py b/tests/test_types.py index 88c8aea..a9f32a7 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -376,3 +376,36 @@ class DictTypeTests(TypeTestHelper, unittest.TestCase): def test_not_equal_to_other_class(self): self.assertFalse(types.Dict() == types.Integer()) + + +class IPAddressTypeTests(TypeTestHelper, unittest.TestCase): + type = types.IPAddress() + + def test_ipv4_address(self): + self.assertConvertedValue('192.168.0.1', '192.168.0.1') + + def test_ipv6_address(self): + self.assertConvertedValue('abcd:ef::1', 'abcd:ef::1') + + def test_strings(self): + self.assertInvalid('') + self.assertInvalid('foo') + + def test_numbers(self): + self.assertInvalid(1) + self.assertInvalid(-1) + self.assertInvalid(3.14) + + +class IPv4AddressTypeTests(IPAddressTypeTests): + type = types.IPAddress(4) + + def test_ipv6_address(self): + self.assertInvalid('abcd:ef::1') + + +class IPv6AddressTypeTests(IPAddressTypeTests): + type = types.IPAddress(6) + + def test_ipv4_address(self): + self.assertInvalid('192.168.0.1') |