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