81981109ee46faa31e2ce04147f447baa4be9069
[ganeti-local] / lib / storage / filestorage.py
1 #
2 #
3
4 # Copyright (C) 2013 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 """File storage functions.
23
24 """
25
26 import logging
27 import os
28
29 from ganeti import compat
30 from ganeti import constants
31 from ganeti import errors
32 from ganeti import pathutils
33 from ganeti import utils
34
35
36 def GetFileStorageSpaceInfo(path):
37   """Retrieves the free and total space of the device where the file is
38      located.
39
40      @type path: string
41      @param path: Path of the file whose embracing device's capacity is
42        reported.
43      @return: a dictionary containing 'vg_size' and 'vg_free' given in MebiBytes
44
45   """
46   try:
47     result = os.statvfs(path)
48     free = (result.f_frsize * result.f_bavail) / (1024 * 1024)
49     size = (result.f_frsize * result.f_blocks) / (1024 * 1024)
50     return {"type": constants.ST_FILE,
51             "name": path,
52             "storage_size": size,
53             "storage_free": free}
54   except OSError, e:
55     raise errors.CommandError("Failed to retrieve file system information about"
56                               " path: %s - %s" % (path, e.strerror))
57
58
59 def _GetForbiddenFileStoragePaths():
60   """Builds a list of path prefixes which shouldn't be used for file storage.
61
62   @rtype: frozenset
63
64   """
65   paths = set([
66     "/boot",
67     "/dev",
68     "/etc",
69     "/home",
70     "/proc",
71     "/root",
72     "/sys",
73     ])
74
75   for prefix in ["", "/usr", "/usr/local"]:
76     paths.update(map(lambda s: "%s/%s" % (prefix, s),
77                      ["bin", "lib", "lib32", "lib64", "sbin"]))
78
79   return compat.UniqueFrozenset(map(os.path.normpath, paths))
80
81
82 def _ComputeWrongFileStoragePaths(paths,
83                                   _forbidden=_GetForbiddenFileStoragePaths()):
84   """Cross-checks a list of paths for prefixes considered bad.
85
86   Some paths, e.g. "/bin", should not be used for file storage.
87
88   @type paths: list
89   @param paths: List of paths to be checked
90   @rtype: list
91   @return: Sorted list of paths for which the user should be warned
92
93   """
94   def _Check(path):
95     return (not os.path.isabs(path) or
96             path in _forbidden or
97             filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
98
99   return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
100
101
102 def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
103   """Returns a list of file storage paths whose prefix is considered bad.
104
105   See L{_ComputeWrongFileStoragePaths}.
106
107   """
108   return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
109
110
111 def _CheckFileStoragePath(path, allowed, exact_match_ok=False):
112   """Checks if a path is in a list of allowed paths for file storage.
113
114   @type path: string
115   @param path: Path to check
116   @type allowed: list
117   @param allowed: List of allowed paths
118   @type exact_match_ok: bool
119   @param exact_match_ok: whether or not it is okay when the path is exactly
120       equal to an allowed path and not a subdir of it
121   @raise errors.FileStoragePathError: If the path is not allowed
122
123   """
124   if not os.path.isabs(path):
125     raise errors.FileStoragePathError("File storage path must be absolute,"
126                                       " got '%s'" % path)
127
128   for i in allowed:
129     if not os.path.isabs(i):
130       logging.info("Ignoring relative path '%s' for file storage", i)
131       continue
132
133     if exact_match_ok:
134       if os.path.normpath(i) == os.path.normpath(path):
135         break
136
137     if utils.IsBelowDir(i, path):
138       break
139   else:
140     raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
141                                       " storage" % path)
142
143
144 def _LoadAllowedFileStoragePaths(filename):
145   """Loads file containing allowed file storage paths.
146
147   @rtype: list
148   @return: List of allowed paths (can be an empty list)
149
150   """
151   try:
152     contents = utils.ReadFile(filename)
153   except EnvironmentError:
154     return []
155   else:
156     return utils.FilterEmptyLinesAndComments(contents)
157
158
159 def CheckFileStoragePathAcceptance(
160     path, _filename=pathutils.FILE_STORAGE_PATHS_FILE,
161     exact_match_ok=False):
162   """Checks if a path is allowed for file storage.
163
164   @type path: string
165   @param path: Path to check
166   @raise errors.FileStoragePathError: If the path is not allowed
167
168   """
169   allowed = _LoadAllowedFileStoragePaths(_filename)
170   if not allowed:
171     raise errors.FileStoragePathError("No paths are valid or path file '%s'"
172                                       " was not accessible." % _filename)
173
174   if _ComputeWrongFileStoragePaths([path]):
175     raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
176                                       path)
177
178   _CheckFileStoragePath(path, allowed, exact_match_ok=exact_match_ok)
179
180
181 def _CheckFileStoragePathExistance(path):
182   """Checks whether the given path is usable on the file system.
183
184   This checks wether the path is existing, a directory and writable.
185
186   @type path: string
187   @param path: path to check
188
189   """
190   if not os.path.isdir(path):
191     raise errors.FileStoragePathError("Path '%s' is not existing or not a"
192                                       " directory." % path)
193   if not os.access(path, os.W_OK):
194     raise errors.FileStoragePathError("Path '%s' is not writable" % path)
195
196
197 def CheckFileStoragePath(
198     path, _allowed_paths_file=pathutils.FILE_STORAGE_PATHS_FILE):
199   """Checks whether the path exists and is acceptable to use.
200
201   @type path: string
202   @param path: path to check
203   @rtype: string
204   @returns: error message if the path is not ready to use
205
206   """
207   try:
208     CheckFileStoragePathAcceptance(path, _filename=_allowed_paths_file,
209                                    exact_match_ok=True)
210   except errors.FileStoragePathError as e:
211     return str(e)
212   if not os.path.isdir(path):
213     return "Path '%s' is not exisiting or not a directory." % path
214   if not os.access(path, os.W_OK):
215     return "Path '%s' is not writable" % path