Merge "Fixing HostName and adding support for HostAddress"

This commit is contained in:
Jenkins 2016-12-07 06:35:28 +00:00 committed by Gerrit Code Review
commit bb8846a3ab
7 changed files with 129 additions and 5 deletions

View File

@ -16,6 +16,7 @@ Option Definitions
.. autoclass:: IPOpt
.. autoclass:: PortOpt
.. autoclass:: HostnameOpt
.. autoclass:: HostAddressOpt
.. autoclass:: URIOpt
.. autoclass:: DeprecatedOpt
.. autoclass:: SubCommandOpt

View File

@ -59,6 +59,7 @@ Type Option
:class:`oslo_config.types.Dict` :class:`oslo_config.cfg.DictOpt`
:class:`oslo_config.types.IPAddress` :class:`oslo_config.cfg.IPOpt`
:class:`oslo_config.types.Hostname` :class:`oslo_config.cfg.HostnameOpt`
:class:`oslo_config.types.HostAddress`:class:`oslo_config.cfg.HostAddressOpt`
:class:`oslo_config.types.URI` :class:`oslo_config.cfg.URIOpt`
==================================== ======
@ -1391,6 +1392,20 @@ class HostnameOpt(Opt):
**kwargs)
class HostAddressOpt(Opt):
"""Option for either an IP or a hostname.
Accepts valid hostnames and valid IP addresses.
.. versionadded:: 3.22
"""
def __init__(self, name, version=None, **kwargs):
super(HostAddressOpt, self).__init__(name,
type=types.HostAddress(version),
**kwargs)
class URIOpt(Opt):
"""Opt with URI type

View File

@ -90,7 +90,8 @@ def _format_defaults(opt):
elif opt.default is None:
default_str = '<None>'
elif (isinstance(opt, (cfg.StrOpt, cfg.IPOpt,
cfg.HostnameOpt, cfg.URIOpt))):
cfg.HostnameOpt, cfg.HostAddressOpt,
cfg.URIOpt))):
default_str = opt.default
elif isinstance(opt, cfg.BoolOpt):
default_str = str(opt.default).lower()

View File

@ -77,6 +77,7 @@ _TYPE_DESCRIPTIONS = {
cfg.PortOpt: 'port number',
cfg.HostnameOpt: 'hostname',
cfg.URIOpt: 'URI',
cfg.HostAddressOpt: 'host address',
cfg._ConfigFileOpt: 'list of filenames',
cfg._ConfigDirOpt: 'list of directory names',
}

View File

@ -707,6 +707,32 @@ class IPv6AddressTypeTests(IPAddressTypeTests):
self.assertInvalid('192.168.0.1')
class HostAddressTypeTests(TypeTestHelper, unittest.TestCase):
type = types.HostAddress()
def test_invalid_host_addresses(self):
self.assertInvalid('-1')
self.assertInvalid('_foo')
self.assertInvalid('3.14')
self.assertInvalid('10.0')
self.assertInvalid('host..name')
self.assertInvalid('org.10')
self.assertInvalid('0.0.00')
def test_valid_host_addresses(self):
self.assertConvertedValue('foo.bar', 'foo.bar')
self.assertConvertedValue('192.168.0.1', '192.168.0.1')
self.assertConvertedValue('abcd:ef::1', 'abcd:ef::1')
self.assertConvertedValue('home-site-here.org.com',
'home-site-here.org.com')
self.assertConvertedValue('3com.com', '3com.com')
self.assertConvertedValue('10.org', '10.org')
self.assertConvertedValue('cell1.nova.site1', 'cell1.nova.site1')
self.assertConvertedValue('ab-c.com', 'ab-c.com')
self.assertConvertedValue('abc.com-org', 'abc.com-org')
self.assertConvertedValue('abc.0-0', 'abc.0-0')
class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
type = types.Hostname()
@ -746,6 +772,12 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
self.assertInvalid(".host.name.com")
self.assertInvalid("no spaces")
def test_invalid_hostnames_with_numeric_characters(self):
self.assertInvalid("10.0.0.0")
self.assertInvalid("3.14")
self.assertInvalid("org.10")
self.assertInvalid('0.0.00')
def test_no_start_end_hyphens(self):
self.assertInvalid("-host.com")
self.assertInvalid("-hostname.com-")
@ -759,9 +791,13 @@ class HostnameTypeTests(TypeTestHelper, unittest.TestCase):
self.assertConvertedEqual('cell1.nova.site1')
self.assertConvertedEqual('site01001')
self.assertConvertedEqual('home-site-here.org.com')
self.assertConvertedEqual('192.168.0.1')
self.assertConvertedEqual('1.1.1')
self.assertConvertedEqual('localhost')
self.assertConvertedEqual('3com.com')
self.assertConvertedEqual('10.org')
self.assertConvertedEqual('10ab.10ab')
self.assertConvertedEqual('ab-c.com')
self.assertConvertedEqual('abc.com-org')
self.assertConvertedEqual('abc.0-0')
def test_max_segment_size(self):
self.assertConvertedEqual('host.%s.com' % ('x' * 63))

View File

@ -704,7 +704,7 @@ class IPAddress(ConfigType):
class Hostname(ConfigType):
"""Hostname type.
"""Host domain name type.
A hostname refers to a valid DNS or hostname. It must not be longer than
253 characters, have a segment greater than 63 characters, nor start or
@ -724,10 +724,14 @@ class Hostname(ConfigType):
- Contains at least one character and a maximum of 63 characters
- Consists only of allowed characters: letters (A-Z and a-z),
digits (0-9), and hyphen (-)
- Ensures that the final segment (representing the top level domain
name) contains at least one non-numeric character
- Does not begin or end with a hyphen
- maximum total length of 253 characters
For more details , please see: http://tools.ietf.org/html/rfc1035
For more details , please see: http://tools.ietf.org/html/rfc1035,
https://www.ietf.org/rfc/rfc1912, and
https://tools.ietf.org/html/rfc1123
"""
if len(value) == 0:
@ -738,6 +742,10 @@ class Hostname(ConfigType):
if value.endswith("."):
value = value[:-1]
allowed = re.compile("(?!-)[A-Z0-9-]{1,63}(?<!-)$", re.IGNORECASE)
if not re.search('[a-zA-Z-]', value.split(".")[-1]):
raise ValueError('%s contains no non-numeric characters in the '
'top-level domain part of the host name and is '
'invalid' % value)
if any((not allowed.match(x)) for x in value.split(".")):
raise ValueError("%s is an invalid hostname" % value)
return value
@ -752,6 +760,55 @@ class Hostname(ConfigType):
return value
class HostAddress(object):
"""Host Address type.
Represents both valid IP addresses and valid host domain names
including fully qualified domain names.
Performs strict checks for both IP addresses and valid hostnames,
matching the opt values to the respective types as per RFC1912.
:param version: defines which version should be explicitly
checked (4 or 6) in case of an IP address
:param type_name: Type name to be used in the sample config file.
"""
def __init__(self, version=None, type_name='host address value'):
"""Check for valid version in case an IP address is provided
"""
self.ip_address = IPAddress(version, type_name)
self.hostname = Hostname('localhost')
def __call__(self, value):
"""Checks if is a valid IP/hostname.
If not a valid IP, makes sure it is not a mistyped IP before
performing checks for it as a hostname.
"""
try:
value = self.ip_address(value)
except ValueError:
try:
value = self.hostname(value)
except ValueError:
raise ValueError("%s is not a valid host address", value)
return value
def __repr__(self):
return 'HostAddress'
def __eq__(self, other):
return self.__class__ == other.__class__
def _formatter(self, value):
return value
class URI(ConfigType):
"""URI type

View File

@ -0,0 +1,13 @@
---
prelude: >
Configuration option type of ``HostAddressOpt`` added to accept and
validate both IP addresses and hostnames. Please refer to the
``features`` section for more information.
features:
- Configuration option type of ``HostAddressOpt`` added to accept both
valid IP address (IPv4 and IPv6) values as well as hostnames.
The ``HostAddressOpt`` will accept both IPv4 and IPv6 addresses
and ensure that strict checks are performed on the IP versions.
This option type will also accept and accurately validate hostnames
ensuring that no invalid IP passes as a valid hostname.