def _flock(self, flag, blocking, timeout, errmsg): """Wrapper for fcntl.flock. @type flag: int @param flag: operation flag @type blocking: bool @param blocking: whether the operation should be done in blocking mode. @type timeout: None or float @param timeout: for how long the operation should be retried (implies non-blocking mode). @type errmsg: string @param errmsg: error message in case operation fails. """ assert self.fd, "Lock was closed" assert timeout is None or timeout >= 0, \ "If specified, timeout must be positive" assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set" # When a timeout is used, LOCK_NB must always be set if not (timeout is None and blocking): flag |= fcntl.LOCK_NB if timeout is None: self._Lock(self.fd, flag, timeout) else: try: retry.Retry(self._Lock, (0.1, 1.2, 1.0), timeout, args=(self.fd, flag, timeout)) except retry.RetryTimeout: raise errors.LockError(errmsg)
def LockFile(fd): """Locks a file using POSIX locks. @type fd: int @param fd: the file descriptor we need to lock """ try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as err: if err.errno == errno.EAGAIN: raise errors.LockError("File already locked") raise
def RequestUnusedUid(all_uids): """Tries to find an unused uid from the uid-pool, locks it and returns it. Usage pattern ============= 1. When starting a process:: from ganeti import ssconf from ganeti import uidpool # Get list of all user-ids in the uid-pool from ssconf ss = ssconf.SimpleStore() uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") all_uids = set(uidpool.ExpandUidPool(uid_pool)) uid = uidpool.RequestUnusedUid(all_uids) try: <start a process with the UID> # Once the process is started, we can release the file lock uid.Unlock() except ..., err: # Return the UID to the pool uidpool.ReleaseUid(uid) 2. Stopping a process:: from ganeti import uidpool uid = <get the UID the process is running under> <stop the process> uidpool.ReleaseUid(uid) @type all_uids: set of integers @param all_uids: a set containing all the user-ids in the user-id pool @return: a LockedUid object representing the unused uid. It's the caller's responsibility to unlock the uid once an instance is started with this uid. """ # Create the lock dir if it's not yet present try: utils.EnsureDirs([(pathutils.UIDPOOL_LOCKDIR, 0755)]) except errors.GenericError, err: raise errors.LockError("Failed to create user-id pool lock dir: %s" % err)
def ReleaseUid(uid): """This should be called when the given user-id is no longer in use. @type uid: LockedUid or integer @param uid: the uid to release back to the pool """ if isinstance(uid, LockedUid): # Make sure we release the exclusive lock, if there is any uid.Unlock() uid_filename = uid.AsStr() else: uid_filename = str(uid) try: uid_path = utils.PathJoin(pathutils.UIDPOOL_LOCKDIR, uid_filename) os.remove(uid_path) except OSError, err: raise errors.LockError("Failed to remove user-id lockfile" " for user-id %s: %s" % (uid_filename, err))
def SafeWriteFile(file_name, file_id, **kwargs): """Wraper over L{WriteFile} that locks the target file. By keeping the target file locked during WriteFile, we ensure that cooperating writers will safely serialise access to the file. @type file_name: str @param file_name: the target filename @type file_id: tuple @param file_id: a result from L{GetFileID} """ fd = os.open(file_name, os.O_RDONLY | os.O_CREAT) try: filelock.LockFile(fd) if file_id is not None: disk_id = GetFileID(fd=fd) if not VerifyFileID(disk_id, file_id): raise errors.LockError( "Cannot overwrite file %s, it has been modified" " since last written" % file_name) return WriteFile(file_name, **kwargs) finally: os.close(fd)
def __check_deleted(self): """Raises an exception if the lock has been deleted. """ if self.__deleted: raise errors.LockError("Deleted lock %s" % self.name)
except errors.GenericError, err: raise errors.LockError("Failed to create user-id pool lock dir: %s" % err) # Get list of currently used uids from the filesystem try: taken_uids = set() for taken_uid in os.listdir(pathutils.UIDPOOL_LOCKDIR): try: taken_uid = int(taken_uid) except ValueError, err: # Skip directory entries that can't be converted into an integer continue taken_uids.add(taken_uid) except OSError, err: raise errors.LockError("Failed to get list of used user-ids: %s" % err) # Filter out spurious entries from the directory listing taken_uids = all_uids.intersection(taken_uids) # Remove the list of used uids from the list of all uids unused_uids = list(all_uids - taken_uids) if not unused_uids: logging.info("All user-ids in the uid-pool are marked 'taken'") # Randomize the order of the unused user-id list random.shuffle(unused_uids) # Randomize the order of the unused user-id list taken_uids = list(taken_uids) random.shuffle(taken_uids)
def RequestUnusedUid(all_uids): """Tries to find an unused uid from the uid-pool, locks it and returns it. Usage pattern ============= 1. When starting a process:: from ganeti import ssconf from ganeti import uidpool # Get list of all user-ids in the uid-pool from ssconf ss = ssconf.SimpleStore() uid_pool = uidpool.ParseUidPool(ss.GetUidPool(), separator="\\n") all_uids = set(uidpool.ExpandUidPool(uid_pool)) uid = uidpool.RequestUnusedUid(all_uids) try: <start a process with the UID> # Once the process is started, we can release the file lock uid.Unlock() except ... as err: # Return the UID to the pool uidpool.ReleaseUid(uid) 2. Stopping a process:: from ganeti import uidpool uid = <get the UID the process is running under> <stop the process> uidpool.ReleaseUid(uid) @type all_uids: set of integers @param all_uids: a set containing all the user-ids in the user-id pool @return: a LockedUid object representing the unused uid. It's the caller's responsibility to unlock the uid once an instance is started with this uid. """ # Create the lock dir if it's not yet present try: utils.EnsureDirs([(pathutils.UIDPOOL_LOCKDIR, 0o755)]) except errors.GenericError as err: raise errors.LockError("Failed to create user-id pool lock dir: %s" % err) # Get list of currently used uids from the filesystem try: taken_uids = set() for taken_uid in os.listdir(pathutils.UIDPOOL_LOCKDIR): try: taken_uid = int(taken_uid) except ValueError as err: # Skip directory entries that can't be converted into an integer continue taken_uids.add(taken_uid) except OSError as err: raise errors.LockError("Failed to get list of used user-ids: %s" % err) # Filter out spurious entries from the directory listing taken_uids = all_uids.intersection(taken_uids) # Remove the list of used uids from the list of all uids unused_uids = list(all_uids - taken_uids) if not unused_uids: logging.info("All user-ids in the uid-pool are marked 'taken'") # Randomize the order of the unused user-id list random.shuffle(unused_uids) # Randomize the order of the unused user-id list taken_uids = list(taken_uids) random.shuffle(taken_uids) for uid in unused_uids + taken_uids: try: # Create the lock file # Note: we don't care if it exists. Only the fact that we can # (or can't) lock it later is what matters. uid_path = utils.PathJoin(pathutils.UIDPOOL_LOCKDIR, str(uid)) lock = utils.FileLock.Open(uid_path) except OSError as err: raise errors.LockError( "Failed to create lockfile for user-id %s: %s" % (uid, err)) try: # Try acquiring an exclusive lock on the lock file lock.Exclusive() # Check if there is any process running with this user-id if _IsUidUsed(uid): logging.debug( "There is already a process running under" " user-id %s", uid) lock.Unlock() continue return LockedUid(uid, lock) except IOError as err: if err.errno == errno.EAGAIN: # The file is already locked, let's skip it and try another unused uid logging.debug("Lockfile for user-id is already locked %s: %s", uid, err) continue except errors.LockError as err: # There was an unexpected error while trying to lock the file logging.error("Failed to lock the lockfile for user-id %s: %s", uid, err) raise raise errors.LockError("Failed to find an unused user-id")