Fixup node disk free/total queries
[ganeti-local] / 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 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 _VIRT_PATH_PREFIX = "/###-VIRTUAL-PATH-###,"
36 _ROOTDIR_ENVNAME = "GANETI_ROOTDIR"
37 _HOSTNAME_ENVNAME = "GANETI_HOSTNAME"
38
39
40 def _GetRootDirectory(envname):
41   """Retrieves root directory from an environment variable.
42
43   @type envname: string
44   @param envname: Environment variable name
45   @rtype: string
46   @return: Root directory (can be empty)
47
48   """
49   path = os.getenv(envname)
50
51   if path:
52     if not os.path.isabs(path):
53       raise RuntimeError("Root directory in '%s' must be absolute: %s" %
54                          (envname, path))
55     return os.path.normpath(path)
56
57   return ""
58
59
60 def _GetHostname(envname):
61   """Retrieves virtual hostname from an environment variable.
62
63   @type envname: string
64   @param envname: Environment variable name
65   @rtype: string
66   @return: Host name (can be empty)
67
68   """
69   return os.getenv(envname, default="")
70
71
72 def _CheckHostname(hostname):
73   """Very basic check for hostnames.
74
75   @type hostname: string
76   @param hostname: Hostname
77
78   """
79   if os.path.basename(hostname) != hostname:
80     raise RuntimeError("Hostname '%s' can not be used for a file system"
81                        " path" % hostname)
82
83
84 def _PreparePaths(rootdir, hostname):
85   """Checks if the root directory and hostname are acceptable.
86
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).
91
92   @type rootdir: string
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
98     and virtual hostname
99
100   """
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))
105
106   if rootdir:
107     assert rootdir == os.path.normpath(rootdir)
108
109     _CheckHostname(hostname)
110
111     if os.path.basename(rootdir) != hostname:
112       raise RuntimeError("Last component of root directory ('%s') must match"
113                          " hostname ('%s')" % (rootdir, hostname))
114
115     return (os.path.dirname(rootdir), rootdir, hostname)
116   else:
117     return ("", "", None)
118
119
120 (_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME) = \
121   _PreparePaths(_GetRootDirectory(_ROOTDIR_ENVNAME),
122                 _GetHostname(_HOSTNAME_ENVNAME))
123
124
125 assert (compat.all([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]) or
126         not compat.any([_VIRT_BASEDIR, _VIRT_NODEROOT, _VIRT_HOSTNAME]))
127
128
129 def GetVirtualHostname():
130   """Returns the virtual hostname.
131
132   @rtype: string or L{None}
133
134   """
135   return _VIRT_HOSTNAME
136
137
138 def _MakeNodeRoot(base, node_name):
139   """Appends a node name to the base directory.
140
141   """
142   _CheckHostname(node_name)
143   return os.path.normpath("%s/%s" % (base, node_name))
144
145
146 def ExchangeNodeRoot(node_name, filename,
147                      _basedir=_VIRT_BASEDIR, _noderoot=_VIRT_NODEROOT):
148   """Replaces the node-specific root directory in a path.
149
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
153   in the environment).
154
155   """
156   if _basedir:
157     pure = _RemoveNodePrefix(filename, _noderoot=_noderoot)
158     result = "%s/%s" % (_MakeNodeRoot(_basedir, node_name), pure)
159   else:
160     result = filename
161
162   return os.path.normpath(result)
163
164
165 def EnvironmentForHost(hostname, _basedir=_VIRT_BASEDIR):
166   """Returns the environment variables for a host.
167
168   """
169   if _basedir:
170     return {
171       _ROOTDIR_ENVNAME: _MakeNodeRoot(_basedir, hostname),
172       _HOSTNAME_ENVNAME: hostname,
173       }
174   else:
175     return {}
176
177
178 def AddNodePrefix(path, _noderoot=_VIRT_NODEROOT):
179   """Adds a node-specific prefix to a path in a virtual cluster.
180
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.
185
186   """
187   assert os.path.isabs(path)
188
189   if _noderoot:
190     result = "%s/%s" % (_noderoot, path)
191   else:
192     result = path
193
194   assert os.path.isabs(result)
195
196   return os.path.normpath(result)
197
198
199 def _RemoveNodePrefix(path, _noderoot=_VIRT_NODEROOT):
200   """Removes the node-specific prefix from a path.
201
202   This is the opposite of L{AddNodePrefix} and removes a node-local prefix
203   path.
204
205   """
206   assert os.path.isabs(path)
207
208   norm_path = os.path.normpath(path)
209
210   if _noderoot:
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])
215
216     if prefix == root_with_sep:
217       result = norm_path[len(norm_root):]
218     else:
219       raise RuntimeError("Path '%s' is not below node root '%s'" %
220                          (path, _noderoot))
221   else:
222     result = norm_path
223
224   assert os.path.isabs(result)
225
226   return result
227
228
229 def MakeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
230   """Virtualizes a path.
231
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.
235
236   """
237   assert os.path.isabs(path)
238
239   if _noderoot:
240     return _VIRT_PATH_PREFIX + _RemoveNodePrefix(path, _noderoot=_noderoot)
241   else:
242     return path
243
244
245 def LocalizeVirtualPath(path, _noderoot=_VIRT_NODEROOT):
246   """Localizes a virtual path.
247
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.
251
252   """
253   assert os.path.isabs(path)
254
255   if _noderoot:
256     if path.startswith(_VIRT_PATH_PREFIX):
257       return AddNodePrefix(path[len(_VIRT_PATH_PREFIX):], _noderoot=_noderoot)
258     else:
259       raise RuntimeError("Path '%s' is not a virtual path" % path)
260   else:
261     return path