Revision 05e733b4
b/Makefile.am | ||
---|---|---|
240 | 240 |
lib/ssh.py \ |
241 | 241 |
lib/storage.py \ |
242 | 242 |
lib/uidpool.py \ |
243 |
lib/vcluster.py \ |
|
243 | 244 |
lib/workerpool.py |
244 | 245 |
|
245 | 246 |
client_PYTHON = \ |
... | ... | |
913 | 914 |
test/ganeti.utils.wrapper_unittest.py \ |
914 | 915 |
test/ganeti.utils.x509_unittest.py \ |
915 | 916 |
test/ganeti.utils_unittest.py \ |
917 |
test/ganeti.vcluster_unittest.py \ |
|
916 | 918 |
test/ganeti.workerpool_unittest.py \ |
917 | 919 |
test/qa.qa_config_unittest.py \ |
918 | 920 |
test/cfgupgrade_unittest.py \ |
b/lib/vcluster.py | ||
---|---|---|
1 |
# |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2012 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Module containing utilities for virtual clusters. |
|
23 |
|
|
24 |
|
|
25 |
""" |
|
26 |
|
|
27 |
import os |
|
28 |
|
|
29 |
from ganeti import compat |
|
30 |
|
|
31 |
|
|
32 |
_VIRT_PATH_PREFIX = "/###-VIRTUAL-PATH-###," |
|
33 |
_ROOTDIR_ENVNAME = "GANETI_ROOTDIR" |
|
34 |
_HOSTNAME_ENVNAME = "GANETI_HOSTNAME" |
|
35 |
|
|
36 |
|
|
37 |
def _GetRootDirectory(envname): |
|
38 |
"""Retrieves root directory from an environment variable. |
|
39 |
|
|
40 |
@type envname: string |
|
41 |
@param envname: Environment variable name |
|
42 |
@rtype: string |
|
43 |
@return: Root directory (can be empty) |
|
44 |
|
|
45 |
""" |
|
46 |
path = os.getenv(envname) |
|
47 |
|
|
48 |
if path: |
|
49 |
if not os.path.isabs(path): |
|
50 |
raise RuntimeError("Root directory in '%s' must be absolute: %s" % |
|
51 |
(envname, path)) |
|
52 |
return os.path.normpath(path) |
|
53 |
|
|
54 |
return "" |
|
55 |
|
|
56 |
|
|
57 |
def _GetHostname(envname): |
|
58 |
"""Retrieves virtual hostname from an environment variable. |
|
59 |
|
|
60 |
@type envname: string |
|
61 |
@param envname: Environment variable name |
|
62 |
@rtype: string |
|
63 |
@return: Host name (can be empty) |
|
64 |
|
|
65 |
""" |
|
66 |
return os.getenv(envname, default="") |
|
67 |
|
|
68 |
|
|
69 |
def _CheckHostname(hostname): |
|
70 |
"""Very basic check for hostnames. |
|
71 |
|
|
72 |
@type hostname: string |
|
73 |
@param hostname: Hostname |
|
74 |
|
|
75 |
""" |
|
76 |
if os.path.basename(hostname) != hostname: |
|
77 |
raise RuntimeError("Hostname '%s' can not be used for a file system" |
|
78 |
" path" % hostname) |
|
79 |
|
|
80 |
|
|
81 |
def _PreparePaths(rootdir, hostname): |
|
82 |
"""Checks if the root directory and hostname are acceptable. |
|
83 |
|
|
84 |
@type rootdir: string |
|
85 |
@param rootdir: Root directory (from environment) |
|
86 |
@type hostname: string |
|
87 |
@param hostname: Hostname (from environment) |
|
88 |
@rtype: tuple; (string, string, string or None) |
|
89 |
@return: Tuple containing cluster-global root directory, node root directory |
|
90 |
and virtual hostname |
|
91 |
|
|
92 |
""" |
|
93 |
if bool(rootdir) ^ bool(hostname): |
|
94 |
raise RuntimeError("Both root directory and hostname must be specified" |
|
95 |
" using the environment variables %s and %s" % |
|
96 |
(_ROOTDIR_ENVNAME, _HOSTNAME_ENVNAME)) |
|
97 |
|
|
98 |
if rootdir: |
|
99 |
assert rootdir == os.path.normpath(rootdir) |
|
100 |
|
|
101 |
_CheckHostname(hostname) |
|
102 |
|
|
103 |
if os.path.basename(rootdir) != hostname: |
|
104 |
raise RuntimeError("Last component of root directory ('%s') must match" |
|
105 |
" hostname ('%s')" % (rootdir, hostname)) |
|
106 |
|
|
107 |
return (os.path.dirname(rootdir), rootdir, hostname) |
|
108 |
else: |
|
109 |
return ("", "", None) |
|
110 |
|
|
111 |
|
|
112 |
(_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME) = \ |
|
113 |
_PreparePaths(_GetRootDirectory(_ROOTDIR_ENVNAME), |
|
114 |
_GetHostname(_HOSTNAME_ENVNAME)) |
|
115 |
|
|
116 |
|
|
117 |
assert (compat.all([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]) or |
|
118 |
not compat.any([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME])) |
|
119 |
|
|
120 |
|
|
121 |
def GetVirtualHostname(): |
|
122 |
"""Returns the virtual hostname. |
|
123 |
|
|
124 |
@rtype: string or L{None} |
|
125 |
|
|
126 |
""" |
|
127 |
return _VIRT_HOSTNAME |
|
128 |
|
|
129 |
|
|
130 |
def _MakeNodeRoot(base, node_name): |
|
131 |
"""Appends a node name to the base directory. |
|
132 |
|
|
133 |
""" |
|
134 |
_CheckHostname(node_name) |
|
135 |
return os.path.normpath("%s/%s" % (base, node_name)) |
|
136 |
|
|
137 |
|
|
138 |
def ExchangeNodeRoot(node_name, filename, |
|
139 |
_basedir=_VIRT_BASEDIR, _noderoot=_VIRT_NODEROOT): |
|
140 |
"""Replaces the node-specific root directory in a path. |
|
141 |
|
|
142 |
Replaces it with the root directory for another node. |
|
143 |
|
|
144 |
""" |
|
145 |
if _basedir: |
|
146 |
pure = _RemoveNodePrefix(filename, _noderoot=_noderoot) |
|
147 |
result = "%s/%s" % (_MakeNodeRoot(_basedir, node_name), pure) |
|
148 |
else: |
|
149 |
result = filename |
|
150 |
|
|
151 |
return os.path.normpath(result) |
|
152 |
|
|
153 |
|
|
154 |
def EnvironmentForHost(hostname, _basedir=_VIRT_BASEDIR): |
|
155 |
"""Returns the environment variables for a host. |
|
156 |
|
|
157 |
""" |
|
158 |
if _basedir: |
|
159 |
return { |
|
160 |
_ROOTDIR_ENVNAME: _MakeNodeRoot(_basedir, hostname), |
|
161 |
_HOSTNAME_ENVNAME: hostname, |
|
162 |
} |
|
163 |
else: |
|
164 |
return {} |
|
165 |
|
|
166 |
|
|
167 |
def AddNodePrefix(path, _noderoot=_VIRT_NODEROOT): |
|
168 |
"""Adds a node-specific prefix to a path in a virtual cluster. |
|
169 |
|
|
170 |
Returned path includes user-specified root directory if specified in |
|
171 |
environment. |
|
172 |
|
|
173 |
""" |
|
174 |
assert os.path.isabs(path) |
|
175 |
|
|
176 |
if _noderoot: |
|
177 |
result = "%s/%s" % (_noderoot, path) |
|
178 |
else: |
|
179 |
result = path |
|
180 |
|
|
181 |
assert os.path.isabs(result) |
|
182 |
|
|
183 |
return os.path.normpath(result) |
|
184 |
|
|
185 |
|
|
186 |
def _RemoveNodePrefix(path, _noderoot=_VIRT_NODEROOT): |
|
187 |
"""Removes the node-specific prefix from a path. |
|
188 |
|
|
189 |
""" |
|
190 |
assert os.path.isabs(path) |
|
191 |
|
|
192 |
norm_path = os.path.normpath(path) |
|
193 |
|
|
194 |
if _noderoot: |
|
195 |
# Make sure path is actually below node root |
|
196 |
norm_root = os.path.normpath(_noderoot) |
|
197 |
root_with_sep = "%s%s" % (norm_root, os.sep) |
|
198 |
prefix = os.path.commonprefix([root_with_sep, norm_path]) |
|
199 |
|
|
200 |
if prefix == root_with_sep: |
|
201 |
result = norm_path[len(norm_root):] |
|
202 |
else: |
|
203 |
raise RuntimeError("Path '%s' is not below node root '%s'" % |
|
204 |
(path, _noderoot)) |
|
205 |
else: |
|
206 |
result = norm_path |
|
207 |
|
|
208 |
assert os.path.isabs(result) |
|
209 |
|
|
210 |
return result |
|
211 |
|
|
212 |
|
|
213 |
def MakeVirtualPath(path, _noderoot=_VIRT_NODEROOT): |
|
214 |
"""Virtualizes a path. |
|
215 |
|
|
216 |
A path is "virtualized" by stripping it of its node-specific directory and |
|
217 |
prepending a prefix (L{_VIRT_PATH_PREFIX}). Use L{LocalizeVirtualPath} to |
|
218 |
undo the process. |
|
219 |
|
|
220 |
""" |
|
221 |
assert os.path.isabs(path) |
|
222 |
|
|
223 |
if _noderoot: |
|
224 |
return _VIRT_PATH_PREFIX + _RemoveNodePrefix(path, _noderoot=_noderoot) |
|
225 |
else: |
|
226 |
return path |
|
227 |
|
|
228 |
|
|
229 |
def LocalizeVirtualPath(path, _noderoot=_VIRT_NODEROOT): |
|
230 |
"""Localizes a virtual path. |
|
231 |
|
|
232 |
A "virtualized" path consists of a prefix (L{LocalizeVirtualPath}) and a |
|
233 |
local path. This function adds the node-specific directory to the local path. |
|
234 |
|
|
235 |
""" |
|
236 |
assert os.path.isabs(path) |
|
237 |
|
|
238 |
if _noderoot: |
|
239 |
if path.startswith(_VIRT_PATH_PREFIX): |
|
240 |
return AddNodePrefix(path[len(_VIRT_PATH_PREFIX):], _noderoot=_noderoot) |
|
241 |
else: |
|
242 |
raise RuntimeError("Path '%s' is not a virtual path" % path) |
|
243 |
else: |
|
244 |
return path |
b/test/ganeti.vcluster_unittest.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
2 |
# |
|
3 |
|
|
4 |
# Copyright (C) 2012 Google Inc. |
|
5 |
# |
|
6 |
# This program is free software; you can redistribute it and/or modify |
|
7 |
# it under the terms of the GNU General Public License as published by |
|
8 |
# the Free Software Foundation; either version 2 of the License, or |
|
9 |
# (at your option) any later version. |
|
10 |
# |
|
11 |
# This program is distributed in the hope that it will be useful, but |
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 |
# General Public License for more details. |
|
15 |
# |
|
16 |
# You should have received a copy of the GNU General Public License |
|
17 |
# along with this program; if not, write to the Free Software |
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
19 |
# 02110-1301, USA. |
|
20 |
|
|
21 |
|
|
22 |
"""Script for testing ganeti.vcluster""" |
|
23 |
|
|
24 |
import os |
|
25 |
import unittest |
|
26 |
|
|
27 |
from ganeti import utils |
|
28 |
from ganeti import compat |
|
29 |
from ganeti import vcluster |
|
30 |
|
|
31 |
import testutils |
|
32 |
|
|
33 |
|
|
34 |
_ENV_DOES_NOT_EXIST = "GANETI_TEST_DOES_NOT_EXIST" |
|
35 |
_ENV_TEST = "GANETI_TESTVAR" |
|
36 |
|
|
37 |
|
|
38 |
class _EnvVarTest(testutils.GanetiTestCase): |
|
39 |
def setUp(self): |
|
40 |
testutils.GanetiTestCase.setUp(self) |
|
41 |
|
|
42 |
os.environ.pop(_ENV_DOES_NOT_EXIST, None) |
|
43 |
os.environ.pop(_ENV_TEST, None) |
|
44 |
|
|
45 |
|
|
46 |
class TestGetRootDirectory(_EnvVarTest): |
|
47 |
def test(self): |
|
48 |
assert os.getenv(_ENV_TEST) is None |
|
49 |
|
|
50 |
self.assertEqual(vcluster._GetRootDirectory(_ENV_DOES_NOT_EXIST), "") |
|
51 |
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "") |
|
52 |
|
|
53 |
# Absolute path |
|
54 |
os.environ[_ENV_TEST] = "/tmp/xy11" |
|
55 |
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "/tmp/xy11") |
|
56 |
|
|
57 |
# Relative path |
|
58 |
os.environ[_ENV_TEST] = "foobar" |
|
59 |
self.assertRaises(RuntimeError, vcluster._GetRootDirectory, _ENV_TEST) |
|
60 |
|
|
61 |
|
|
62 |
|
|
63 |
class TestGetHostname(_EnvVarTest): |
|
64 |
def test(self): |
|
65 |
assert os.getenv(_ENV_TEST) is None |
|
66 |
|
|
67 |
self.assertEqual(vcluster._GetRootDirectory(_ENV_DOES_NOT_EXIST), "") |
|
68 |
self.assertEqual(vcluster._GetRootDirectory(_ENV_TEST), "") |
|
69 |
|
|
70 |
os.environ[_ENV_TEST] = "some.host.example.com" |
|
71 |
self.assertEqual(vcluster._GetHostname(_ENV_TEST), "some.host.example.com") |
|
72 |
|
|
73 |
|
|
74 |
class TestCheckHostname(_EnvVarTest): |
|
75 |
def test(self): |
|
76 |
for i in ["/", "/tmp"]: |
|
77 |
self.assertRaises(RuntimeError, vcluster._CheckHostname, i) |
|
78 |
|
|
79 |
|
|
80 |
class TestPreparePaths(_EnvVarTest): |
|
81 |
def testInvalidParameters(self): |
|
82 |
self.assertRaises(RuntimeError, vcluster._PreparePaths, |
|
83 |
None, "host.example.com") |
|
84 |
self.assertRaises(RuntimeError, vcluster._PreparePaths, |
|
85 |
"/tmp/", "") |
|
86 |
|
|
87 |
def testNonNormalizedRootDir(self): |
|
88 |
self.assertRaises(AssertionError, vcluster._PreparePaths, |
|
89 |
"/tmp////xyz//", "host.example.com") |
|
90 |
|
|
91 |
def testInvalidHostname(self): |
|
92 |
self.assertRaises(RuntimeError, vcluster._PreparePaths, "/tmp", "/") |
|
93 |
|
|
94 |
def testPathHostnameMismatch(self): |
|
95 |
self.assertRaises(RuntimeError, vcluster._PreparePaths, |
|
96 |
"/tmp/host.example.com", "server.example.com") |
|
97 |
|
|
98 |
def testNoVirtCluster(self): |
|
99 |
for i in ["", None]: |
|
100 |
self.assertEqual(vcluster._PreparePaths(i, i), ("", "", None)) |
|
101 |
|
|
102 |
def testVirtCluster(self): |
|
103 |
self.assertEqual(vcluster._PreparePaths("/tmp/host.example.com", |
|
104 |
"host.example.com"), |
|
105 |
("/tmp", "/tmp/host.example.com", "host.example.com")) |
|
106 |
|
|
107 |
|
|
108 |
class TestMakeNodeRoot(unittest.TestCase): |
|
109 |
def test(self): |
|
110 |
self.assertRaises(RuntimeError, vcluster._MakeNodeRoot, "/tmp", "/") |
|
111 |
|
|
112 |
for i in ["/tmp", "/tmp/", "/tmp///"]: |
|
113 |
self.assertEqual(vcluster._MakeNodeRoot(i, "other.example.com"), |
|
114 |
"/tmp/other.example.com") |
|
115 |
|
|
116 |
|
|
117 |
class TestEnvironmentForHost(unittest.TestCase): |
|
118 |
def test(self): |
|
119 |
self.assertEqual(vcluster.EnvironmentForHost("host.example.com", |
|
120 |
_basedir=None), |
|
121 |
{}) |
|
122 |
for i in ["host.example.com", "other.example.com"]: |
|
123 |
self.assertEqual(vcluster.EnvironmentForHost(i, _basedir="/tmp"), { |
|
124 |
vcluster._ROOTDIR_ENVNAME: "/tmp/%s" % i, |
|
125 |
vcluster._HOSTNAME_ENVNAME: i, |
|
126 |
}) |
|
127 |
|
|
128 |
|
|
129 |
class TestExchangeNodeRoot(unittest.TestCase): |
|
130 |
def test(self): |
|
131 |
result = vcluster.ExchangeNodeRoot("node1.example.com", "/tmp/file", |
|
132 |
_basedir=None, _noderoot=None) |
|
133 |
self.assertEqual(result, "/tmp/file") |
|
134 |
|
|
135 |
self.assertRaises(RuntimeError, vcluster.ExchangeNodeRoot, |
|
136 |
"node1.example.com", "/tmp/node1.example.com", |
|
137 |
_basedir="/tmp", |
|
138 |
_noderoot="/tmp/nodeZZ.example.com") |
|
139 |
|
|
140 |
result = vcluster.ExchangeNodeRoot("node2.example.com", |
|
141 |
"/tmp/node1.example.com/file", |
|
142 |
_basedir="/tmp", |
|
143 |
_noderoot="/tmp/node1.example.com") |
|
144 |
self.assertEqual(result, "/tmp/node2.example.com/file") |
|
145 |
|
|
146 |
|
|
147 |
class TestAddNodePrefix(unittest.TestCase): |
|
148 |
def testRelativePath(self): |
|
149 |
self.assertRaises(AssertionError, vcluster.AddNodePrefix, |
|
150 |
"foobar", _noderoot=None) |
|
151 |
|
|
152 |
def testRelativeNodeRoot(self): |
|
153 |
self.assertRaises(AssertionError, vcluster.AddNodePrefix, |
|
154 |
"/tmp", _noderoot="foobar") |
|
155 |
|
|
156 |
def test(self): |
|
157 |
path = vcluster.AddNodePrefix("/file/path", |
|
158 |
_noderoot="/tmp/node1.example.com/") |
|
159 |
self.assertEqual(path, "/tmp/node1.example.com/file/path") |
|
160 |
|
|
161 |
self.assertEqual(vcluster.AddNodePrefix("/file/path", _noderoot=""), |
|
162 |
"/file/path") |
|
163 |
|
|
164 |
|
|
165 |
class TestRemoveNodePrefix(unittest.TestCase): |
|
166 |
def testRelativePath(self): |
|
167 |
self.assertRaises(AssertionError, vcluster._RemoveNodePrefix, |
|
168 |
"foobar", _noderoot=None) |
|
169 |
|
|
170 |
def testOutsideNodeRoot(self): |
|
171 |
self.assertRaises(RuntimeError, vcluster._RemoveNodePrefix, |
|
172 |
"/file/path", _noderoot="/tmp/node1.example.com") |
|
173 |
self.assertRaises(RuntimeError, vcluster._RemoveNodePrefix, |
|
174 |
"/tmp/xyzfile", _noderoot="/tmp/xyz") |
|
175 |
|
|
176 |
def test(self): |
|
177 |
path = vcluster._RemoveNodePrefix("/tmp/node1.example.com/file/path", |
|
178 |
_noderoot="/tmp/node1.example.com") |
|
179 |
self.assertEqual(path, "/file/path") |
|
180 |
|
|
181 |
path = vcluster._RemoveNodePrefix("/file/path", _noderoot=None) |
|
182 |
self.assertEqual(path, "/file/path") |
|
183 |
|
|
184 |
|
|
185 |
class TestMakeVirtualPath(unittest.TestCase): |
|
186 |
def testRelativePath(self): |
|
187 |
self.assertRaises(AssertionError, vcluster.MakeVirtualPath, |
|
188 |
"foobar", _noderoot=None) |
|
189 |
|
|
190 |
def testOutsideNodeRoot(self): |
|
191 |
self.assertRaises(RuntimeError, vcluster.MakeVirtualPath, |
|
192 |
"/file/path", _noderoot="/tmp/node1.example.com") |
|
193 |
|
|
194 |
def testWithNodeRoot(self): |
|
195 |
path = vcluster.MakeVirtualPath("/tmp/node1.example.com/tmp/file", |
|
196 |
_noderoot="/tmp/node1.example.com") |
|
197 |
self.assertEqual(path, "%s/tmp/file" % vcluster._VIRT_PATH_PREFIX) |
|
198 |
|
|
199 |
def testNormal(self): |
|
200 |
self.assertEqual(vcluster.MakeVirtualPath("/tmp/file", _noderoot=None), |
|
201 |
"/tmp/file") |
|
202 |
|
|
203 |
|
|
204 |
class TestLocalizeVirtualPath(unittest.TestCase): |
|
205 |
def testWrongPrefix(self): |
|
206 |
self.assertRaises(RuntimeError, vcluster.LocalizeVirtualPath, |
|
207 |
"/tmp/some/path", _noderoot="/tmp/node1.example.com") |
|
208 |
|
|
209 |
def testCorrectPrefixRelativePath(self): |
|
210 |
self.assertRaises(AssertionError, vcluster.LocalizeVirtualPath, |
|
211 |
vcluster._VIRT_PATH_PREFIX + "foobar", |
|
212 |
_noderoot="/tmp/node1.example.com") |
|
213 |
|
|
214 |
def testWithNodeRoot(self): |
|
215 |
lvp = vcluster.LocalizeVirtualPath |
|
216 |
|
|
217 |
virtpath1 = "%s/tmp/file" % vcluster._VIRT_PATH_PREFIX |
|
218 |
virtpath2 = "%s////tmp////file" % vcluster._VIRT_PATH_PREFIX |
|
219 |
|
|
220 |
for i in [virtpath1, virtpath2]: |
|
221 |
result = lvp(i, _noderoot="/tmp/node1.example.com") |
|
222 |
self.assertEqual(result, "/tmp/node1.example.com/tmp/file") |
|
223 |
|
|
224 |
def testNormal(self): |
|
225 |
self.assertEqual(vcluster.LocalizeVirtualPath("/tmp/file", _noderoot=None), |
|
226 |
"/tmp/file") |
|
227 |
|
|
228 |
|
|
229 |
class TestVirtualPathPrefix(unittest.TestCase): |
|
230 |
def test(self): |
|
231 |
self.assertTrue(os.path.isabs(vcluster._VIRT_PATH_PREFIX)) |
|
232 |
self.assertEqual(os.path.normcase(vcluster._VIRT_PATH_PREFIX), |
|
233 |
vcluster._VIRT_PATH_PREFIX) |
|
234 |
|
|
235 |
|
|
236 |
if __name__ == "__main__": |
|
237 |
testutils.GanetiTestProgram() |
Also available in: Unified diff