4 # Copyright (C) 2012 Google Inc.
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.
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.
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
22 """Module containing utilities for virtual clusters.
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.
32 from ganeti import compat
35 _VIRT_PATH_PREFIX = "/###-VIRTUAL-PATH-###,"
36 _ROOTDIR_ENVNAME = "GANETI_ROOTDIR"
37 _HOSTNAME_ENVNAME = "GANETI_HOSTNAME"
40 def _GetRootDirectory(envname):
41 """Retrieves root directory from an environment variable.
44 @param envname: Environment variable name
46 @return: Root directory (can be empty)
49 path = os.getenv(envname)
52 if not os.path.isabs(path):
53 raise RuntimeError("Root directory in '%s' must be absolute: %s" %
55 return os.path.normpath(path)
60 def _GetHostname(envname):
61 """Retrieves virtual hostname from an environment variable.
64 @param envname: Environment variable name
66 @return: Host name (can be empty)
69 return os.getenv(envname, default="")
72 def _CheckHostname(hostname):
73 """Very basic check for hostnames.
75 @type hostname: string
76 @param hostname: Hostname
79 if os.path.basename(hostname) != hostname:
80 raise RuntimeError("Hostname '%s' can not be used for a file system"
84 def _PreparePaths(rootdir, hostname):
85 """Checks if the root directory and hostname are acceptable.
87 The (node-specific) root directory must have the hostname as its last
88 component. The parent directory then becomes the cluster-wide root directory.
89 This is necessary as some components must be able to predict the root path on
90 a remote node (e.g. copying files via scp).
93 @param rootdir: Root directory (from environment)
94 @type hostname: string
95 @param hostname: Hostname (from environment)
96 @rtype: tuple; (string, string, string or None)
97 @return: Tuple containing cluster-global root directory, node root directory
101 if bool(rootdir) ^ bool(hostname):
102 raise RuntimeError("Both root directory and hostname must be specified"
103 " using the environment variables %s and %s" %
104 (_ROOTDIR_ENVNAME, _HOSTNAME_ENVNAME))
107 assert rootdir == os.path.normpath(rootdir)
109 _CheckHostname(hostname)
111 if os.path.basename(rootdir) != hostname:
112 raise RuntimeError("Last component of root directory ('%s') must match"
113 " hostname ('%s')" % (rootdir, hostname))
115 return (os.path.dirname(rootdir), rootdir, hostname)
117 return ("", "", None)
120 (_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME) = \
121 _PreparePaths(_GetRootDirectory(_ROOTDIR_ENVNAME),
122 _GetHostname(_HOSTNAME_ENVNAME))
125 assert (compat.all([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]) or
126 not compat.any([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]))
129 def GetVirtualHostname():
130 """Returns the virtual hostname.
132 @rtype: string or L{None}
135 return _VIRT_HOSTNAME
138 def _MakeNodeRoot(base, node_name):
139 """Appends a node name to the base directory.
142 _CheckHostname(node_name)
143 return os.path.normpath("%s/%s" % (base, node_name))
146 def ExchangeNodeRoot(node_name, filename,
147 _basedir=_VIRT_BASEDIR, _noderoot=_VIRT_NODEROOT):
148 """Replaces the node-specific root directory in a path.
150 Replaces it with the root directory for another node. Assuming
151 C{/tmp/vcluster/node1} is the root directory for C{node1}, the result will be
152 C{/tmp/vcluster/node3} for C{node3} (as long as a root directory is specified
157 pure = _RemoveNodePrefix(filename, _noderoot=_noderoot)
158 result = "%s/%s" % (_MakeNodeRoot(_basedir, node_name), pure)
162 return os.path.normpath(result)
165 def EnvironmentForHost(hostname, _basedir=_VIRT_BASEDIR):
166 """Returns the environment variables for a host.
171 _ROOTDIR_ENVNAME: _MakeNodeRoot(_basedir, hostname),
172 _HOSTNAME_ENVNAME: hostname,
178 def AddNodePrefix(path, _noderoot=_VIRT_NODEROOT):
179 """Adds a node-specific prefix to a path in a virtual cluster.
181 Returned path includes user-specified root directory if specified in
182 environment. As an example, the path C{/var/lib/ganeti} becomes
183 C{/tmp/vcluster/node1/var/lib/ganeti} if C{/tmp/vcluster/node1} is the root
184 directory specified in the environment.
187 assert os.path.isabs(path)
190 result = "%s/%s" % (_noderoot, path)
194 assert os.path.isabs(result)
196 return os.path.normpath(result)
199 def _RemoveNodePrefix(path, _noderoot=_VIRT_NODEROOT):
200 """Removes the node-specific prefix from a path.
202 This is the opposite of L{AddNodePrefix} and removes a node-local prefix
206 assert os.path.isabs(path)
208 norm_path = os.path.normpath(path)
211 # Make sure path is actually below node root
212 norm_root = os.path.normpath(_noderoot)
213 root_with_sep = "%s%s" % (norm_root, os.sep)
214 prefix = os.path.commonprefix([root_with_sep, norm_path])
216 if prefix == root_with_sep:
217 result = norm_path[len(norm_root):]
219 raise RuntimeError("Path '%s' is not below node root '%s'" %
224 assert os.path.isabs(result)
229 def MakeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
230 """Virtualizes a path.
232 A path is "virtualized" by stripping it of its node-specific directory and
233 prepending a prefix (L{_VIRT_PATH_PREFIX}). Use L{LocalizeVirtualPath} to
234 undo the process. Virtual paths are meant to be transported via RPC.
237 assert os.path.isabs(path)
240 return _VIRT_PATH_PREFIX + _RemoveNodePrefix(path, _noderoot=_noderoot)
245 def LocalizeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
246 """Localizes a virtual path.
248 A "virtualized" path consists of a prefix (L{LocalizeVirtualPath}) and a
249 local path. This function adds the node-specific directory to the local path.
250 Virtual paths are meant to be transported via RPC.
253 assert os.path.isabs(path)
256 if path.startswith(_VIRT_PATH_PREFIX):
257 return AddNodePrefix(path[len(_VIRT_PATH_PREFIX):], _noderoot=_noderoot)
259 raise RuntimeError("Path '%s' is not a virtual path" % path)