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.file_path_acceptance_fn = _file_path_acceptance_fn
95 self.path = path
96
97 - def Exists(self, assert_exists=None):
98 """Check for the existence of the given file.
99
100 @param assert_exists: creates an assertion on the result value:
101 - if true, raise errors.BlockDeviceError if the file doesn't exist
102 - if false, raise errors.BlockDeviceError if the file does exist
103 @rtype: boolean
104 @return: True if the file exists
105
106 """
107
108 exists = os.path.isfile(self.path)
109
110 if not exists and assert_exists is True:
111 raise base.ThrowError("%s: No such file", self.path)
112 if exists and assert_exists is False:
113 raise base.ThrowError("%s: File exists", self.path)
114
115 return exists
116
118 """Remove the file backing the block device.
119
120 @rtype: boolean
121 @return: True if the removal was successful
122
123 """
124 try:
125 os.remove(self.path)
126 return True
127 except OSError as err:
128 if err.errno != errno.ENOENT:
129 base.ThrowError("%s: can't remove: %s", self.path, err)
130 return False
131
133 """Return the actual disk size in bytes.
134
135 @rtype: int
136 @return: The file size in bytes.
137
138 """
139 self.Exists(assert_exists=True)
140 try:
141 return os.stat(self.path).st_size
142 except OSError as err:
143 base.ThrowError("%s: can't stat: %s", self.path, err)
144
145 - def Grow(self, amount, dryrun, backingstore, _excl_stor):
146 """Grow the file
147
148 @param amount: the amount (in mebibytes) to grow by.
149
150 """
151
152 self.Exists(assert_exists=True)
153
154 if amount < 0:
155 base.ThrowError("%s: can't grow by negative amount", self.path)
156
157 if dryrun:
158 return
159 if not backingstore:
160 return
161
162 current_size = self.Size()
163 new_size = current_size + amount * 1024 * 1024
164 try:
165 f = open(self.path, "a+")
166 f.truncate(new_size)
167 f.close()
168 except EnvironmentError, err:
169 base.ThrowError("%s: can't grow: ", self.path, str(err))
170
171 - def Move(self, new_path):
172 """Move file to a location inside the file storage dir.
173
174 """
175
176 self.Exists(assert_exists=True)
177 self.file_path_acceptance_fn(new_path)
178 try:
179 os.rename(self.path, new_path)
180 self.path = new_path
181 except OSError, err:
182 base.ThrowError("%s: can't rename to %s: ", str(err), new_path)
183
186 """File device.
187
188 This class represents a file storage backend device.
189
190 The unique_id for the file device is a (file_driver, file_path) tuple.
191
192 """
193 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
194 """Initalizes a file device backend.
195
196 """
197 if children:
198 raise errors.BlockDeviceError("Invalid setup for file device")
199 super(FileStorage, self).__init__(unique_id, children, size, params,
200 dyn_params, *args)
201 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
202 raise ValueError("Invalid configuration data %s" % str(unique_id))
203 self.driver = unique_id[0]
204 self.dev_path = unique_id[1]
205 self.file = FileDeviceHelper(self.dev_path)
206 self.Attach()
207
209 """Assemble the device.
210
211 Checks whether the file device exists, raises BlockDeviceError otherwise.
212
213 """
214 self.file.Exists(assert_exists=True)
215
217 """Shutdown the device.
218
219 This is a no-op for the file type, as we don't deactivate
220 the file on shutdown.
221
222 """
223 pass
224
225 - def Open(self, force=False, exclusive=True):
226 """Make the device ready for I/O.
227
228 This is a no-op for the file type.
229
230 """
231 pass
232
234 """Notifies that the device will no longer be used for I/O.
235
236 This is a no-op for the file type.
237
238 """
239 pass
240
242 """Remove the file backing the block device.
243
244 @rtype: boolean
245 @return: True if the removal was successful
246
247 """
248 return self.file.Remove()
249
251 """Renames the file.
252
253 """
254 return self.file.Move(new_id[1])
255
256 - def Grow(self, amount, dryrun, backingstore, excl_stor):
257 """Grow the file
258
259 @param amount: the amount (in mebibytes) to grow with
260
261 """
262 if not backingstore:
263 return
264 if dryrun:
265 return
266 self.file.Grow(amount, dryrun, backingstore, excl_stor)
267
269 """Attach to an existing file.
270
271 Check if this file already exists.
272
273 @rtype: boolean
274 @return: True if file exists
275
276 """
277 self.attached = self.file.Exists()
278 return self.attached
279
281 """Return the actual disk size.
282
283 @note: the device needs to be active when this is called
284
285 """
286 return self.file.Size()
287
288 @classmethod
289 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
290 dyn_params, *args):
291 """Create a new file.
292
293 @type size: int
294 @param size: the size of file in MiB
295
296 @rtype: L{bdev.FileStorage}
297 @return: an instance of FileStorage
298
299 """
300 if excl_stor:
301 raise errors.ProgrammerError("FileStorage device requested with"
302 " exclusive_storage")
303 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
304 raise ValueError("Invalid configuration data %s" % str(unique_id))
305
306 dev_path = unique_id[1]
307
308 FileDeviceHelper.CreateFile(dev_path, size)
309 return FileStorage(unique_id, children, size, params, dyn_params,
310 *args)
311
314 """Retrieves the free and total space of the device where the file is
315 located.
316
317 @type path: string
318 @param path: Path of the file whose embracing device's capacity is
319 reported.
320 @return: a dictionary containing 'vg_size' and 'vg_free' given in MebiBytes
321
322 """
323 try:
324 result = os.statvfs(path)
325 free = (result.f_frsize * result.f_bavail) / (1024 * 1024)
326 size = (result.f_frsize * result.f_blocks) / (1024 * 1024)
327 return {"type": constants.ST_FILE,
328 "name": path,
329 "storage_size": size,
330 "storage_free": free}
331 except OSError, e:
332 raise errors.CommandError("Failed to retrieve file system information about"
333 " path: %s - %s" % (path, e.strerror))
334
337 """Builds a list of path prefixes which shouldn't be used for file storage.
338
339 @rtype: frozenset
340
341 """
342 paths = set([
343 "/boot",
344 "/dev",
345 "/etc",
346 "/home",
347 "/proc",
348 "/root",
349 "/sys",
350 ])
351
352 for prefix in ["", "/usr", "/usr/local"]:
353 paths.update(map(lambda s: "%s/%s" % (prefix, s),
354 ["bin", "lib", "lib32", "lib64", "sbin"]))
355
356 return compat.UniqueFrozenset(map(os.path.normpath, paths))
357
361 """Cross-checks a list of paths for prefixes considered bad.
362
363 Some paths, e.g. "/bin", should not be used for file storage.
364
365 @type paths: list
366 @param paths: List of paths to be checked
367 @rtype: list
368 @return: Sorted list of paths for which the user should be warned
369
370 """
371 def _Check(path):
372 return (not os.path.isabs(path) or
373 path in _forbidden or
374 filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
375
376 return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
377
386
389 """Checks if a path is in a list of allowed paths for file storage.
390
391 @type path: string
392 @param path: Path to check
393 @type allowed: list
394 @param allowed: List of allowed paths
395 @type exact_match_ok: bool
396 @param exact_match_ok: whether or not it is okay when the path is exactly
397 equal to an allowed path and not a subdir of it
398 @raise errors.FileStoragePathError: If the path is not allowed
399
400 """
401 if not os.path.isabs(path):
402 raise errors.FileStoragePathError("File storage path must be absolute,"
403 " got '%s'" % path)
404
405 for i in allowed:
406 if not os.path.isabs(i):
407 logging.info("Ignoring relative path '%s' for file storage", i)
408 continue
409
410 if exact_match_ok:
411 if os.path.normpath(i) == os.path.normpath(path):
412 break
413
414 if utils.IsBelowDir(i, path):
415 break
416 else:
417 raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
418 " storage" % path)
419
422 """Loads file containing allowed file storage paths.
423
424 @rtype: list
425 @return: List of allowed paths (can be an empty list)
426
427 """
428 try:
429 contents = utils.ReadFile(filename)
430 except EnvironmentError:
431 return []
432 else:
433 return utils.FilterEmptyLinesAndComments(contents)
434
456
459 """Checks whether the given path is usable on the file system.
460
461 This checks wether the path is existing, a directory and writable.
462
463 @type path: string
464 @param path: path to check
465
466 """
467 if not os.path.isdir(path):
468 raise errors.FileStoragePathError("Path '%s' does not exist or is not a"
469 " directory." % path)
470 if not os.access(path, os.W_OK):
471 raise errors.FileStoragePathError("Path '%s' is not writable" % path)
472
476 """Checks whether the path exists and is acceptable to use.
477
478 Can be used for any file-based storage, for example shared-file storage.
479
480 @type path: string
481 @param path: path to check
482 @rtype: string
483 @returns: error message if the path is not ready to use
484
485 """
486 try:
487 CheckFileStoragePathAcceptance(path, _filename=_allowed_paths_file,
488 exact_match_ok=True)
489 except errors.FileStoragePathError as e:
490 return str(e)
491 if not os.path.isdir(path):
492 return "Path '%s' is not existing or not a directory." % path
493 if not os.access(path, os.W_OK):
494 return "Path '%s' is not writable" % path
495