1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Filesystem-based access functions and disk templates.
32
33 """
34
35 import logging
36 import errno
37 import os
38
39 from ganeti import compat
40 from ganeti import constants
41 from ganeti import errors
42 from ganeti import pathutils
43 from ganeti import utils
44 from ganeti.utils import io
45 from ganeti.storage import base
49
50 @classmethod
51 - def CreateFile(cls, path, size, create_folders=False,
52 _file_path_acceptance_fn=None):
53 """Create a new file and its file device helper.
54
55 @param size: the size in MiBs the file should be truncated to.
56 @param create_folders: create the directories for the path if necessary
57 (using L{ganeti.utils.io.Makedirs})
58
59 @rtype: FileDeviceHelper
60 @return: The FileDeviceHelper object representing the object.
61 @raise errors.FileStoragePathError: if the file path is disallowed by policy
62
63 """
64
65 if not _file_path_acceptance_fn:
66 _file_path_acceptance_fn = CheckFileStoragePathAcceptance
67 _file_path_acceptance_fn(path)
68
69 if create_folders:
70 folder = os.path.dirname(path)
71 io.Makedirs(folder)
72
73 try:
74 fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
75 f = os.fdopen(fd, "w")
76 f.truncate(size * 1024 * 1024)
77 f.close()
78 except EnvironmentError as err:
79 base.ThrowError("%s: can't create: %s", path, str(err))
80
81 return FileDeviceHelper(path,
82 _file_path_acceptance_fn=_file_path_acceptance_fn)
83
84 - def __init__(self, path, _file_path_acceptance_fn=None):
85 """Create a new file device helper.
86
87 @raise errors.FileStoragePathError: if the file path is disallowed by policy
88
89 """
90 if not _file_path_acceptance_fn:
91 _file_path_acceptance_fn = CheckFileStoragePathAcceptance
92 _file_path_acceptance_fn(path)
93
94 self.path = path
95
96 - def Exists(self, assert_exists=None):
97 """Check for the existence of the given file.
98
99 @param assert_exists: creates an assertion on the result value:
100 - if true, raise errors.BlockDeviceError if the file doesn't exist
101 - if false, raise errors.BlockDeviceError if the file does exist
102 @rtype: boolean
103 @return: True if the file exists
104
105 """
106
107 exists = os.path.isfile(self.path)
108
109 if not exists and assert_exists is True:
110 raise base.ThrowError("%s: No such file", self.path)
111 if exists and assert_exists is False:
112 raise base.ThrowError("%s: File exists", self.path)
113
114 return exists
115
117 """Remove the file backing the block device.
118
119 @rtype: boolean
120 @return: True if the removal was successful
121
122 """
123 try:
124 os.remove(self.path)
125 return True
126 except OSError as err:
127 if err.errno != errno.ENOENT:
128 base.ThrowError("%s: can't remove: %s", self.path, err)
129 return False
130
132 """Return the actual disk size in bytes.
133
134 @rtype: int
135 @return: The file size in bytes.
136
137 """
138 self.Exists(assert_exists=True)
139 try:
140 return os.stat(self.path).st_size
141 except OSError as err:
142 base.ThrowError("%s: can't stat: %s", self.path, err)
143
144 - def Grow(self, amount, dryrun, backingstore, _excl_stor):
145 """Grow the file
146
147 @param amount: the amount (in mebibytes) to grow by.
148
149 """
150
151 self.Exists(assert_exists=True)
152
153 if amount < 0:
154 base.ThrowError("%s: can't grow by negative amount", self.path)
155
156 if dryrun:
157 return
158 if not backingstore:
159 return
160
161 current_size = self.Size()
162 new_size = current_size + amount * 1024 * 1024
163 try:
164 f = open(self.path, "a+")
165 f.truncate(new_size)
166 f.close()
167 except EnvironmentError, err:
168 base.ThrowError("%s: can't grow: ", self.path, str(err))
169
172 """File device.
173
174 This class represents a file storage backend device.
175
176 The unique_id for the file device is a (file_driver, file_path) tuple.
177
178 """
179 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
180 """Initalizes a file device backend.
181
182 """
183 if children:
184 raise errors.BlockDeviceError("Invalid setup for file device")
185 super(FileStorage, self).__init__(unique_id, children, size, params,
186 dyn_params, *args)
187 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
188 raise ValueError("Invalid configuration data %s" % str(unique_id))
189 self.driver = unique_id[0]
190 self.dev_path = unique_id[1]
191 self.file = FileDeviceHelper(self.dev_path)
192 self.Attach()
193
195 """Assemble the device.
196
197 Checks whether the file device exists, raises BlockDeviceError otherwise.
198
199 """
200 self.file.Exists(assert_exists=True)
201
203 """Shutdown the device.
204
205 This is a no-op for the file type, as we don't deactivate
206 the file on shutdown.
207
208 """
209 pass
210
211 - def Open(self, force=False):
212 """Make the device ready for I/O.
213
214 This is a no-op for the file type.
215
216 """
217 pass
218
220 """Notifies that the device will no longer be used for I/O.
221
222 This is a no-op for the file type.
223
224 """
225 pass
226
228 """Remove the file backing the block device.
229
230 @rtype: boolean
231 @return: True if the removal was successful
232
233 """
234 return self.file.Remove()
235
237 """Renames the file.
238
239 """
240
241 base.ThrowError("Rename is not supported for file-based storage")
242
243 - def Grow(self, amount, dryrun, backingstore, excl_stor):
244 """Grow the file
245
246 @param amount: the amount (in mebibytes) to grow with
247
248 """
249 if not backingstore:
250 return
251 if dryrun:
252 return
253 self.file.Grow(amount, dryrun, backingstore, excl_stor)
254
256 """Attach to an existing file.
257
258 Check if this file already exists.
259
260 @rtype: boolean
261 @return: True if file exists
262
263 """
264 self.attached = self.file.Exists()
265 return self.attached
266
268 """Return the actual disk size.
269
270 @note: the device needs to be active when this is called
271
272 """
273 return self.file.Size()
274
275 @classmethod
276 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
277 dyn_params, *args):
278 """Create a new file.
279
280 @type size: int
281 @param size: the size of file in MiB
282
283 @rtype: L{bdev.FileStorage}
284 @return: an instance of FileStorage
285
286 """
287 if excl_stor:
288 raise errors.ProgrammerError("FileStorage device requested with"
289 " exclusive_storage")
290 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
291 raise ValueError("Invalid configuration data %s" % str(unique_id))
292
293 dev_path = unique_id[1]
294
295 FileDeviceHelper.CreateFile(dev_path, size)
296 return FileStorage(unique_id, children, size, params, dyn_params,
297 *args)
298
301 """Retrieves the free and total space of the device where the file is
302 located.
303
304 @type path: string
305 @param path: Path of the file whose embracing device's capacity is
306 reported.
307 @return: a dictionary containing 'vg_size' and 'vg_free' given in MebiBytes
308
309 """
310 try:
311 result = os.statvfs(path)
312 free = (result.f_frsize * result.f_bavail) / (1024 * 1024)
313 size = (result.f_frsize * result.f_blocks) / (1024 * 1024)
314 return {"type": constants.ST_FILE,
315 "name": path,
316 "storage_size": size,
317 "storage_free": free}
318 except OSError, e:
319 raise errors.CommandError("Failed to retrieve file system information about"
320 " path: %s - %s" % (path, e.strerror))
321
324 """Builds a list of path prefixes which shouldn't be used for file storage.
325
326 @rtype: frozenset
327
328 """
329 paths = set([
330 "/boot",
331 "/dev",
332 "/etc",
333 "/home",
334 "/proc",
335 "/root",
336 "/sys",
337 ])
338
339 for prefix in ["", "/usr", "/usr/local"]:
340 paths.update(map(lambda s: "%s/%s" % (prefix, s),
341 ["bin", "lib", "lib32", "lib64", "sbin"]))
342
343 return compat.UniqueFrozenset(map(os.path.normpath, paths))
344
348 """Cross-checks a list of paths for prefixes considered bad.
349
350 Some paths, e.g. "/bin", should not be used for file storage.
351
352 @type paths: list
353 @param paths: List of paths to be checked
354 @rtype: list
355 @return: Sorted list of paths for which the user should be warned
356
357 """
358 def _Check(path):
359 return (not os.path.isabs(path) or
360 path in _forbidden or
361 filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
362
363 return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
364
373
376 """Checks if a path is in a list of allowed paths for file storage.
377
378 @type path: string
379 @param path: Path to check
380 @type allowed: list
381 @param allowed: List of allowed paths
382 @type exact_match_ok: bool
383 @param exact_match_ok: whether or not it is okay when the path is exactly
384 equal to an allowed path and not a subdir of it
385 @raise errors.FileStoragePathError: If the path is not allowed
386
387 """
388 if not os.path.isabs(path):
389 raise errors.FileStoragePathError("File storage path must be absolute,"
390 " got '%s'" % path)
391
392 for i in allowed:
393 if not os.path.isabs(i):
394 logging.info("Ignoring relative path '%s' for file storage", i)
395 continue
396
397 if exact_match_ok:
398 if os.path.normpath(i) == os.path.normpath(path):
399 break
400
401 if utils.IsBelowDir(i, path):
402 break
403 else:
404 raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
405 " storage" % path)
406
409 """Loads file containing allowed file storage paths.
410
411 @rtype: list
412 @return: List of allowed paths (can be an empty list)
413
414 """
415 try:
416 contents = utils.ReadFile(filename)
417 except EnvironmentError:
418 return []
419 else:
420 return utils.FilterEmptyLinesAndComments(contents)
421
443
446 """Checks whether the given path is usable on the file system.
447
448 This checks wether the path is existing, a directory and writable.
449
450 @type path: string
451 @param path: path to check
452
453 """
454 if not os.path.isdir(path):
455 raise errors.FileStoragePathError("Path '%s' does not exist or is not a"
456 " directory." % path)
457 if not os.access(path, os.W_OK):
458 raise errors.FileStoragePathError("Path '%s' is not writable" % path)
459
463 """Checks whether the path exists and is acceptable to use.
464
465 Can be used for any file-based storage, for example shared-file storage.
466
467 @type path: string
468 @param path: path to check
469 @rtype: string
470 @returns: error message if the path is not ready to use
471
472 """
473 try:
474 CheckFileStoragePathAcceptance(path, _filename=_allowed_paths_file,
475 exact_match_ok=True)
476 except errors.FileStoragePathError as e:
477 return str(e)
478 if not os.path.isdir(path):
479 return "Path '%s' is not existing or not a directory." % path
480 if not os.access(path, os.W_OK):
481 return "Path '%s' is not writable" % path
482