From 26288e6803bc2976ce51805f2af43dfd28e32d1d Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Fri, 5 Mar 2010 16:55:27 +0100 Subject: [PATCH] Add a function to validate and normalize hostnames This differs slightly from the specification, by allowing names to start with digits, not checking the length of individual components, and allowing underscores. Signed-off-by: Iustin Pop <iustin@google.com> Reviewed-by: Michael Hanselmann <hansmi@google.com> --- lib/utils.py | 23 ++++++++++++++++++++ test/ganeti.utils_unittest.py | 41 +++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lib/utils.py b/lib/utils.py index 52b799fe5..131f4c1a7 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -615,6 +615,8 @@ class HostInfo: """Class implementing resolver and hostname functionality """ + _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$") + def __init__(self, name=None): """Initialize the host name object. @@ -665,6 +667,27 @@ class HostInfo: return result + @classmethod + def NormalizeName(cls, hostname): + """Validate and normalize the given hostname. + + @attention: the validation is a bit more relaxed than the standards + require; most importantly, we allow underscores in names + @raise errors.OpPrereqError: when the name is not valid + + """ + hostname = hostname.lower() + if (not cls._VALID_NAME_RE.match(hostname) or + # double-dots, meaning empty label + ".." in hostname or + # empty initial label + hostname.startswith(".")): + raise errors.OpPrereqError("Invalid hostname '%s'" % hostname, + errors.ECODE_INVAL) + if hostname.endswith("."): + hostname = hostname.rstrip(".") + return hostname + def GetHostInfo(name=None): """Lookup host name and raise an OpPrereqError for failures""" diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py index aad16c101..8392ad056 100755 --- a/test/ganeti.utils_unittest.py +++ b/test/ganeti.utils_unittest.py @@ -47,10 +47,10 @@ from ganeti.utils import IsProcessAlive, RunCmd, \ ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles, \ SetEtcHostsEntry, RemoveEtcHostsEntry, FirstFree, OwnIpAddress, \ TailFile, ForceDictType, SafeEncode, IsNormAbsPath, FormatTime, \ - UnescapeAndSplit, RunParts, PathJoin + UnescapeAndSplit, RunParts, PathJoin, HostInfo from ganeti.errors import LockError, UnitParseError, GenericError, \ - ProgrammerError + ProgrammerError, OpPrereqError class TestIsProcessAlive(unittest.TestCase): @@ -1284,5 +1284,42 @@ class TestPathJoin(unittest.TestCase): self.failUnlessRaises(ValueError, PathJoin, "/a", "/b") +class TestHostInfo(unittest.TestCase): + """Testing case for HostInfo""" + + def testUppercase(self): + data = "AbC.example.com" + self.failUnlessEqual(HostInfo.NormalizeName(data), data.lower()) + + def testTooLongName(self): + data = "a.b." + "c" * 255 + self.failUnlessRaises(OpPrereqError, HostInfo.NormalizeName, data) + + def testTrailingDot(self): + data = "a.b.c" + self.failUnlessEqual(HostInfo.NormalizeName(data + "."), data) + + def testInvalidName(self): + data = [ + "a b", + "a/b", + ".a.b", + "a..b", + ] + for value in data: + self.failUnlessRaises(OpPrereqError, HostInfo.NormalizeName, value) + + def testValidName(self): + data = [ + "a.b", + "a-b", + "a_b", + "a.b.c", + ] + for value in data: + HostInfo.NormalizeName(value) + + + if __name__ == '__main__': testutils.GanetiTestProgram() -- GitLab