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.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
117 - def Remove(self):
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
132 - def Size(self):
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 # Check that the file exists 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 # Check that the file exists 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
184 185 -class FileStorage(base.BlockDev):
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
208 - def Assemble(self):
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
216 - def Shutdown(self):
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
233 - def Close(self):
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
241 - def Remove(self):
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
250 - def Rename(self, new_id):
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
268 - def Attach(self):
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
280 - def GetActualSize(self):
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
312 313 -def GetFileStorageSpaceInfo(path):
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
335 336 -def _GetForbiddenFileStoragePaths():
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
358 359 -def _ComputeWrongFileStoragePaths(paths, 360 _forbidden=_GetForbiddenFileStoragePaths()):
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
378 379 -def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
380 """Returns a list of file storage paths whose prefix is considered bad. 381 382 See L{_ComputeWrongFileStoragePaths}. 383 384 """ 385 return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
386
387 388 -def _CheckFileStoragePath(path, allowed, exact_match_ok=False):
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
420 421 -def _LoadAllowedFileStoragePaths(filename):
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
435 436 -def CheckFileStoragePathAcceptance( 437 path, _filename=pathutils.FILE_STORAGE_PATHS_FILE, 438 exact_match_ok=False):
439 """Checks if a path is allowed for file storage. 440 441 @type path: string 442 @param path: Path to check 443 @raise errors.FileStoragePathError: If the path is not allowed 444 445 """ 446 allowed = _LoadAllowedFileStoragePaths(_filename) 447 if not allowed: 448 raise errors.FileStoragePathError("No paths are valid or path file '%s'" 449 " was not accessible." % _filename) 450 451 if _ComputeWrongFileStoragePaths([path]): 452 raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" % 453 path) 454 455 _CheckFileStoragePath(path, allowed, exact_match_ok=exact_match_ok)
456
457 458 -def _CheckFileStoragePathExistance(path):
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
473 474 -def CheckFileStoragePath( 475 path, _allowed_paths_file=pathutils.FILE_STORAGE_PATHS_FILE):
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