Package ganeti :: Package storage :: Module filestorage
[hide private]
[frames] | no frames]

Source Code for Module ganeti.storage.filestorage

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2013 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 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 
46 47 48 -class FileDeviceHelper(object):
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
116 - def Remove(self):
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
131 - def Size(self):
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 # Check that the file exists 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
170 171 -class FileStorage(base.BlockDev):
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
194 - def Assemble(self):
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
202 - def Shutdown(self):
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
219 - def Close(self):
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
227 - def Remove(self):
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
236 - def Rename(self, new_id):
237 """Renames the file. 238 239 """ 240 # TODO: implement rename for file-based storage 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
255 - def Attach(self):
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
267 - def GetActualSize(self):
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
299 300 -def GetFileStorageSpaceInfo(path):
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
322 323 -def _GetForbiddenFileStoragePaths():
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
345 346 -def _ComputeWrongFileStoragePaths(paths, 347 _forbidden=_GetForbiddenFileStoragePaths()):
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
365 366 -def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
367 """Returns a list of file storage paths whose prefix is considered bad. 368 369 See L{_ComputeWrongFileStoragePaths}. 370 371 """ 372 return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
373
374 375 -def _CheckFileStoragePath(path, allowed, exact_match_ok=False):
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
407 408 -def _LoadAllowedFileStoragePaths(filename):
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
422 423 -def CheckFileStoragePathAcceptance( 424 path, _filename=pathutils.FILE_STORAGE_PATHS_FILE, 425 exact_match_ok=False):
426 """Checks if a path is allowed for file storage. 427 428 @type path: string 429 @param path: Path to check 430 @raise errors.FileStoragePathError: If the path is not allowed 431 432 """ 433 allowed = _LoadAllowedFileStoragePaths(_filename) 434 if not allowed: 435 raise errors.FileStoragePathError("No paths are valid or path file '%s'" 436 " was not accessible." % _filename) 437 438 if _ComputeWrongFileStoragePaths([path]): 439 raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" % 440 path) 441 442 _CheckFileStoragePath(path, allowed, exact_match_ok=exact_match_ok)
443
444 445 -def _CheckFileStoragePathExistance(path):
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
460 461 -def CheckFileStoragePath( 462 path, _allowed_paths_file=pathutils.FILE_STORAGE_PATHS_FILE):
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