Commit 05e733b4 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Add new module for virtual clusters



This module will take care of managing paths for virtual clusters.
Unittests are included (100% coverage).
Signed-off-by: default avatarMichael Hanselmann <hansmi@google.com>
Reviewed-by: default avatarRené Nussbaumer <rn@google.com>
parent 93b19295
......@@ -240,6 +240,7 @@ pkgpython_PYTHON = \
lib/ssh.py \
lib/storage.py \
lib/uidpool.py \
lib/vcluster.py \
lib/workerpool.py
client_PYTHON = \
......@@ -913,6 +914,7 @@ python_tests = \
test/ganeti.utils.wrapper_unittest.py \
test/ganeti.utils.x509_unittest.py \
test/ganeti.utils_unittest.py \
test/ganeti.vcluster_unittest.py \
test/ganeti.workerpool_unittest.py \
test/qa.qa_config_unittest.py \
test/cfgupgrade_unittest.py \
......
#
#
# Copyright (C) 2012 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Module containing utilities for virtual clusters.
"""
import os
from ganeti import compat
_VIRT_PATH_PREFIX = "/###-VIRTUAL-PATH-###,"
_ROOTDIR_ENVNAME = "GANETI_ROOTDIR"
_HOSTNAME_ENVNAME = "GANETI_HOSTNAME"
def _GetRootDirectory(envname):
"""Retrieves root directory from an environment variable.
@type envname: string
@param envname: Environment variable name
@rtype: string
@return: Root directory (can be empty)
"""
path = os.getenv(envname)
if path:
if not os.path.isabs(path):
raise RuntimeError("Root directory in '%s' must be absolute: %s" %
(envname, path))
return os.path.normpath(path)
return ""
def _GetHostname(envname):
"""Retrieves virtual hostname from an environment variable.
@type envname: string
@param envname: Environment variable name
@rtype: string
@return: Host name (can be empty)
"""
return os.getenv(envname, default="")
def _CheckHostname(hostname):
"""Very basic check for hostnames.
@type hostname: string
@param hostname: Hostname
"""
if os.path.basename(hostname) != hostname:
raise RuntimeError("Hostname '%s' can not be used for a file system"
" path" % hostname)
def _PreparePaths(rootdir, hostname):
"""Checks if the root directory and hostname are acceptable.
@type rootdir: string
@param rootdir: Root directory (from environment)
@type hostname: string
@param hostname: Hostname (from environment)
@rtype: tuple; (string, string, string or None)
@return: Tuple containing cluster-global root directory, node root directory
and virtual hostname
"""
if bool(rootdir) ^ bool(hostname):
raise RuntimeError("Both root directory and hostname must be specified"
" using the environment variables %s and %s" %
(_ROOTDIR_ENVNAME, _HOSTNAME_ENVNAME))
if rootdir:
assert rootdir == os.path.normpath(rootdir)
_CheckHostname(hostname)
if os.path.basename(rootdir) != hostname:
raise RuntimeError("Last component of root directory ('%s') must match"
" hostname ('%s')" % (rootdir, hostname))
return (os.path.dirname(rootdir), rootdir, hostname)
else:
return ("", "", None)
(_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME) = \
_PreparePaths(_GetRootDirectory(_ROOTDIR_ENVNAME),
_GetHostname(_HOSTNAME_ENVNAME))
assert (compat.all([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]) or
not compat.any([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]))
def GetVirtualHostname():
"""Returns the virtual hostname.
@rtype: string or L{None}
"""
return _VIRT_HOSTNAME
def _MakeNodeRoot(base, node_name):
"""Appends a node name to the base directory.
"""
_CheckHostname(node_name)
return os.path.normpath("%s/%s" % (base, node_name))
def ExchangeNodeRoot(node_name, filename,
_basedir=_VIRT_BASEDIR, _noderoot=_VIRT_NODEROOT):
"""Replaces the node-specific root directory in a path.
Replaces it with the root directory for another node.
"""
if _basedir:
pure = _RemoveNodePrefix(filename, _noderoot=_noderoot)
result = "%s/%s" % (_MakeNodeRoot(_basedir, node_name), pure)
else:
result = filename
return os.path.normpath(result)
def EnvironmentForHost(hostname, _basedir=_VIRT_BASEDIR):
"""Returns the environment variables for a host.
"""
if _basedir:
return {
_ROOTDIR_ENVNAME: _MakeNodeRoot(_basedir, hostname),
_HOSTNAME_ENVNAME: hostname,
}
else:
return {}
def AddNodePrefix(path, _noderoot=_VIRT_NODEROOT):
"""Adds a node-specific prefix to a path in a virtual cluster.
Returned path includes user-specified root directory if specified in
environment.
"""
assert os.path.isabs(path)
if _noderoot:
result = "%s/%s" % (_noderoot, path)
else:
result = path
assert os.path.isabs(result)
return os.path.normpath(result)
def _RemoveNodePrefix(path, _noderoot=_VIRT_NODEROOT):
"""Removes the node-specific prefix from a path.
"""
assert os.path.isabs(path)
norm_path = os.path.normpath(path)
if _noderoot:
# Make sure path is actually below node root
norm_root = os.path.normpath(_noderoot)
root_with_sep = "%s%s" % (norm_root, os.sep)
prefix = os.path.commonprefix([root_with_sep, norm_path])
if prefix == root_with_sep:
result = norm_path[len(norm_root):]
else:
raise RuntimeError("Path '%s' is not below node root '%s'" %
(path, _noderoot))
else:
result = norm_path
assert os.path.isabs(result)
return result
def MakeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
"""Virtualizes a path.
A path is "virtualized" by stripping it of its node-specific directory and
prepending a prefix (L{_VIRT_PATH_PREFIX}). Use L{LocalizeVirtualPath} to
undo the process.
"""
assert os.path.isabs(path)
if _noderoot:
return _VIRT_PATH_PREFIX + _RemoveNodePrefix(path, _noderoot=_noderoot)
else:
return path
def LocalizeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
"""Localizes a virtual path.
A "virtualized" path consists of a prefix (L{LocalizeVirtualPath}) and a
local path. This function adds the node-specific directory to the local path.
"""
assert os.path.isabs(path)
if _noderoot:
if path.startswith(_VIRT_PATH_PREFIX):
return AddNodePrefix(path[len(_VIRT_PATH_PREFIX):], _noderoot=_noderoot)
else:
raise RuntimeError("Path '%s' is not a virtual path" % path)
else:
return path
#!/usr/bin/python
#
# Copyright (C) 2012 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Script for testing ganeti.vcluster"""
import os
import unittest
from ganeti import utils
from ganeti import compat
from ganeti import vcluster
import testutils
_ENV_DOES_NOT_EXIST = "GANETI_TEST_DOES_NOT_EXIST"
_ENV_TEST = "GANETI_TESTVAR"
class _EnvVarTest(testutils.GanetiTestCase):
def setUp(self):
testutils.GanetiTestCase.setUp(self)
os.environ.pop(_ENV_DOES_NOT_EXIST, None)
os.environ.pop(_ENV_TEST, None)
class TestGetRootDirectory(_EnvVarTest):
def test(self):
assert os.getenv(_ENV_TEST) is None
self.assertEqual(vcluster._GetRootDirectory(_ENV_DOES_NOT_EXIST), "")
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "")
# Absolute path
os.environ[_ENV_TEST] = "/tmp/xy11"
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "/tmp/xy11")
# Relative path
os.environ[_ENV_TEST] = "foobar"
self.assertRaises(RuntimeError, vcluster._GetRootDirectory, _ENV_TEST)
class TestGetHostname(_EnvVarTest):
def test(self):
assert os.getenv(_ENV_TEST) is None
self.assertEqual(vcluster._GetRootDirectory(_ENV_DOES_NOT_EXIST), "")
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "")
os.environ[_ENV_TEST] = "some.host.example.com"
self.assertEqual(vcluster._GetHostname(_ENV_TEST), "some.host.example.com")
class TestCheckHostname(_EnvVarTest):
def test(self):
for i in ["/", "/tmp"]:
self.assertRaises(RuntimeError, vcluster._CheckHostname, i)
class TestPreparePaths(_EnvVarTest):
def testInvalidParameters(self):
self.assertRaises(RuntimeError, vcluster._PreparePaths,
None, "host.example.com")
self.assertRaises(RuntimeError, vcluster._PreparePaths,
"/tmp/", "")
def testNonNormalizedRootDir(self):
self.assertRaises(AssertionError, vcluster._PreparePaths,
"/tmp////xyz//", "host.example.com")
def testInvalidHostname(self):
self.assertRaises(RuntimeError, vcluster._PreparePaths, "/tmp", "/")
def testPathHostnameMismatch(self):
self.assertRaises(RuntimeError, vcluster._PreparePaths,
"/tmp/host.example.com", "server.example.com")
def testNoVirtCluster(self):
for i in ["", None]:
self.assertEqual(vcluster._PreparePaths(i, i), ("", "", None))
def testVirtCluster(self):
self.assertEqual(vcluster._PreparePaths("/tmp/host.example.com",
"host.example.com"),
("/tmp", "/tmp/host.example.com", "host.example.com"))
class TestMakeNodeRoot(unittest.TestCase):
def test(self):
self.assertRaises(RuntimeError, vcluster._MakeNodeRoot, "/tmp", "/")
for i in ["/tmp", "/tmp/", "/tmp///"]:
self.assertEqual(vcluster._MakeNodeRoot(i, "other.example.com"),
"/tmp/other.example.com")
class TestEnvironmentForHost(unittest.TestCase):
def test(self):
self.assertEqual(vcluster.EnvironmentForHost("host.example.com",
_basedir=None),
{})
for i in ["host.example.com", "other.example.com"]:
self.assertEqual(vcluster.EnvironmentForHost(i, _basedir="/tmp"), {
vcluster._ROOTDIR_ENVNAME: "/tmp/%s" % i,
vcluster._HOSTNAME_ENVNAME: i,
})
class TestExchangeNodeRoot(unittest.TestCase):
def test(self):
result = vcluster.ExchangeNodeRoot("node1.example.com", "/tmp/file",
_basedir=None, _noderoot=None)
self.assertEqual(result, "/tmp/file")
self.assertRaises(RuntimeError, vcluster.ExchangeNodeRoot,
"node1.example.com", "/tmp/node1.example.com",
_basedir="/tmp",
_noderoot="/tmp/nodeZZ.example.com")
result = vcluster.ExchangeNodeRoot("node2.example.com",
"/tmp/node1.example.com/file",
_basedir="/tmp",
_noderoot="/tmp/node1.example.com")
self.assertEqual(result, "/tmp/node2.example.com/file")
class TestAddNodePrefix(unittest.TestCase):
def testRelativePath(self):
self.assertRaises(AssertionError, vcluster.AddNodePrefix,
"foobar", _noderoot=None)
def testRelativeNodeRoot(self):
self.assertRaises(AssertionError, vcluster.AddNodePrefix,
"/tmp", _noderoot="foobar")
def test(self):
path = vcluster.AddNodePrefix("/file/path",
_noderoot="/tmp/node1.example.com/")
self.assertEqual(path, "/tmp/node1.example.com/file/path")
self.assertEqual(vcluster.AddNodePrefix("/file/path", _noderoot=""),
"/file/path")
class TestRemoveNodePrefix(unittest.TestCase):
def testRelativePath(self):
self.assertRaises(AssertionError, vcluster._RemoveNodePrefix,
"foobar", _noderoot=None)
def testOutsideNodeRoot(self):
self.assertRaises(RuntimeError, vcluster._RemoveNodePrefix,
"/file/path", _noderoot="/tmp/node1.example.com")
self.assertRaises(RuntimeError, vcluster._RemoveNodePrefix,
"/tmp/xyzfile", _noderoot="/tmp/xyz")
def test(self):
path = vcluster._RemoveNodePrefix("/tmp/node1.example.com/file/path",
_noderoot="/tmp/node1.example.com")
self.assertEqual(path, "/file/path")
path = vcluster._RemoveNodePrefix("/file/path", _noderoot=None)
self.assertEqual(path, "/file/path")
class TestMakeVirtualPath(unittest.TestCase):
def testRelativePath(self):
self.assertRaises(AssertionError, vcluster.MakeVirtualPath,
"foobar", _noderoot=None)
def testOutsideNodeRoot(self):
self.assertRaises(RuntimeError, vcluster.MakeVirtualPath,
"/file/path", _noderoot="/tmp/node1.example.com")
def testWithNodeRoot(self):
path = vcluster.MakeVirtualPath("/tmp/node1.example.com/tmp/file",
_noderoot="/tmp/node1.example.com")
self.assertEqual(path, "%s/tmp/file" % vcluster._VIRT_PATH_PREFIX)
def testNormal(self):
self.assertEqual(vcluster.MakeVirtualPath("/tmp/file", _noderoot=None),
"/tmp/file")
class TestLocalizeVirtualPath(unittest.TestCase):
def testWrongPrefix(self):
self.assertRaises(RuntimeError, vcluster.LocalizeVirtualPath,
"/tmp/some/path", _noderoot="/tmp/node1.example.com")
def testCorrectPrefixRelativePath(self):
self.assertRaises(AssertionError, vcluster.LocalizeVirtualPath,
vcluster._VIRT_PATH_PREFIX + "foobar",
_noderoot="/tmp/node1.example.com")
def testWithNodeRoot(self):
lvp = vcluster.LocalizeVirtualPath
virtpath1 = "%s/tmp/file" % vcluster._VIRT_PATH_PREFIX
virtpath2 = "%s////tmp////file" % vcluster._VIRT_PATH_PREFIX
for i in [virtpath1, virtpath2]:
result = lvp(i, _noderoot="/tmp/node1.example.com")
self.assertEqual(result, "/tmp/node1.example.com/tmp/file")
def testNormal(self):
self.assertEqual(vcluster.LocalizeVirtualPath("/tmp/file", _noderoot=None),
"/tmp/file")
class TestVirtualPathPrefix(unittest.TestCase):
def test(self):
self.assertTrue(os.path.isabs(vcluster._VIRT_PATH_PREFIX))
self.assertEqual(os.path.normcase(vcluster._VIRT_PATH_PREFIX),
vcluster._VIRT_PATH_PREFIX)
if __name__ == "__main__":
testutils.GanetiTestProgram()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment