Statistics
| Branch: | Tag: | Revision:

root / lib / vcluster.py @ 9c8c69bc

History | View | Annotate | Download (7.4 kB)

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
Most functions manipulate file system paths and are no-ops when the environment
25
variables C{GANETI_ROOTDIR} and C{GANETI_HOSTNAME} are not set. See the
26
functions' docstrings for details.
27

28
"""
29

    
30
import os
31

    
32
from ganeti import compat
33

    
34

    
35
ETC_HOSTS = "/etc/hosts"
36

    
37
_VIRT_PATH_PREFIX = "/###-VIRTUAL-PATH-###,"
38
_ROOTDIR_ENVNAME = "GANETI_ROOTDIR"
39
_HOSTNAME_ENVNAME = "GANETI_HOSTNAME"
40

    
41
#: List of paths which shouldn't be virtualized
42
_VPATH_WHITELIST = compat.UniqueFrozenset([
43
  ETC_HOSTS,
44
  ])
45

    
46

    
47
def _GetRootDirectory(envname):
48
  """Retrieves root directory from an environment variable.
49

50
  @type envname: string
51
  @param envname: Environment variable name
52
  @rtype: string
53
  @return: Root directory (can be empty)
54

55
  """
56
  path = os.getenv(envname)
57

    
58
  if path:
59
    if not os.path.isabs(path):
60
      raise RuntimeError("Root directory in '%s' must be absolute: %s" %
61
                         (envname, path))
62
    return os.path.normpath(path)
63

    
64
  return ""
65

    
66

    
67
def _GetHostname(envname):
68
  """Retrieves virtual hostname from an environment variable.
69

70
  @type envname: string
71
  @param envname: Environment variable name
72
  @rtype: string
73
  @return: Host name (can be empty)
74

75
  """
76
  return os.getenv(envname, default="")
77

    
78

    
79
def _CheckHostname(hostname):
80
  """Very basic check for hostnames.
81

82
  @type hostname: string
83
  @param hostname: Hostname
84

85
  """
86
  if os.path.basename(hostname) != hostname:
87
    raise RuntimeError("Hostname '%s' can not be used for a file system"
88
                       " path" % hostname)
89

    
90

    
91
def _PreparePaths(rootdir, hostname):
92
  """Checks if the root directory and hostname are acceptable.
93

94
  The (node-specific) root directory must have the hostname as its last
95
  component. The parent directory then becomes the cluster-wide root directory.
96
  This is necessary as some components must be able to predict the root path on
97
  a remote node (e.g. copying files via scp).
98

99
  @type rootdir: string
100
  @param rootdir: Root directory (from environment)
101
  @type hostname: string
102
  @param hostname: Hostname (from environment)
103
  @rtype: tuple; (string, string, string or None)
104
  @return: Tuple containing cluster-global root directory, node root directory
105
    and virtual hostname
106

107
  """
108
  if bool(rootdir) ^ bool(hostname):
109
    raise RuntimeError("Both root directory and hostname must be specified"
110
                       " using the environment variables %s and %s" %
111
                       (_ROOTDIR_ENVNAME, _HOSTNAME_ENVNAME))
112

    
113
  if rootdir:
114
    assert rootdir == os.path.normpath(rootdir)
115

    
116
    _CheckHostname(hostname)
117

    
118
    if os.path.basename(rootdir) != hostname:
119
      raise RuntimeError("Last component of root directory ('%s') must match"
120
                         " hostname ('%s')" % (rootdir, hostname))
121

    
122
    return (os.path.dirname(rootdir), rootdir, hostname)
123
  else:
124
    return ("", "", None)
125

    
126

    
127
(_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME) = \
128
  _PreparePaths(_GetRootDirectory(_ROOTDIR_ENVNAME),
129
                _GetHostname(_HOSTNAME_ENVNAME))
130

    
131

    
132
assert (compat.all([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]) or
133
        not compat.any([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]))
134

    
135

    
136
def GetVirtualHostname():
137
  """Returns the virtual hostname.
138

139
  @rtype: string or L{None}
140

141
  """
142
  return _VIRT_HOSTNAME
143

    
144

    
145
def MakeNodeRoot(base, node_name):
146
  """Appends a node name to the base directory.
147

148
  """
149
  _CheckHostname(node_name)
150
  return os.path.normpath("%s/%s" % (base, node_name))
151

    
152

    
153
def ExchangeNodeRoot(node_name, filename,
154
                     _basedir=_VIRT_BASEDIR, _noderoot=_VIRT_NODEROOT):
155
  """Replaces the node-specific root directory in a path.
156

157
  Replaces it with the root directory for another node. Assuming
158
  C{/tmp/vcluster/node1} is the root directory for C{node1}, the result will be
159
  C{/tmp/vcluster/node3} for C{node3} (as long as a root directory is specified
160
  in the environment).
161

162
  """
163
  if _basedir:
164
    pure = _RemoveNodePrefix(filename, _noderoot=_noderoot)
165
    result = "%s/%s" % (MakeNodeRoot(_basedir, node_name), pure)
166
  else:
167
    result = filename
168

    
169
  return os.path.normpath(result)
170

    
171

    
172
def EnvironmentForHost(hostname, _basedir=_VIRT_BASEDIR):
173
  """Returns the environment variables for a host.
174

175
  """
176
  if _basedir:
177
    return {
178
      _ROOTDIR_ENVNAME: MakeNodeRoot(_basedir, hostname),
179
      _HOSTNAME_ENVNAME: hostname,
180
      }
181
  else:
182
    return {}
183

    
184

    
185
def AddNodePrefix(path, _noderoot=_VIRT_NODEROOT):
186
  """Adds a node-specific prefix to a path in a virtual cluster.
187

188
  Returned path includes user-specified root directory if specified in
189
  environment. As an example, the path C{/var/lib/ganeti} becomes
190
  C{/tmp/vcluster/node1/var/lib/ganeti} if C{/tmp/vcluster/node1} is the root
191
  directory specified in the environment.
192

193
  """
194
  assert os.path.isabs(path)
195

    
196
  if _noderoot:
197
    result = "%s/%s" % (_noderoot, path)
198
  else:
199
    result = path
200

    
201
  assert os.path.isabs(result)
202

    
203
  return os.path.normpath(result)
204

    
205

    
206
def _RemoveNodePrefix(path, _noderoot=_VIRT_NODEROOT):
207
  """Removes the node-specific prefix from a path.
208

209
  This is the opposite of L{AddNodePrefix} and removes a node-local prefix
210
  path.
211

212
  """
213
  assert os.path.isabs(path)
214

    
215
  norm_path = os.path.normpath(path)
216

    
217
  if _noderoot:
218
    # Make sure path is actually below node root
219
    norm_root = os.path.normpath(_noderoot)
220
    root_with_sep = "%s%s" % (norm_root, os.sep)
221
    prefix = os.path.commonprefix([root_with_sep, norm_path])
222

    
223
    if prefix == root_with_sep:
224
      result = norm_path[len(norm_root):]
225
    else:
226
      raise RuntimeError("Path '%s' is not below node root '%s'" %
227
                         (path, _noderoot))
228
  else:
229
    result = norm_path
230

    
231
  assert os.path.isabs(result)
232

    
233
  return result
234

    
235

    
236
def MakeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
237
  """Virtualizes a path.
238

239
  A path is "virtualized" by stripping it of its node-specific directory and
240
  prepending a prefix (L{_VIRT_PATH_PREFIX}). Use L{LocalizeVirtualPath} to
241
  undo the process. Virtual paths are meant to be transported via RPC.
242

243
  """
244
  assert os.path.isabs(path)
245

    
246
  if _noderoot and path not in _VPATH_WHITELIST:
247
    return _VIRT_PATH_PREFIX + _RemoveNodePrefix(path, _noderoot=_noderoot)
248
  else:
249
    return path
250

    
251

    
252
def LocalizeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
253
  """Localizes a virtual path.
254

255
  A "virtualized" path consists of a prefix (L{LocalizeVirtualPath}) and a
256
  local path. This function adds the node-specific directory to the local path.
257
  Virtual paths are meant to be transported via RPC.
258

259
  """
260
  assert os.path.isabs(path)
261

    
262
  if _noderoot and path not in _VPATH_WHITELIST:
263
    if path.startswith(_VIRT_PATH_PREFIX):
264
      return AddNodePrefix(path[len(_VIRT_PATH_PREFIX):], _noderoot=_noderoot)
265
    else:
266
      raise RuntimeError("Path '%s' is not a virtual path" % path)
267
  else:
268
    return path