From 3865ca483fad871b93711962d86a5abe653fb316 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Mon, 10 Jan 2011 20:44:41 +0100
Subject: [PATCH] utils: Move I/O-related code into separate file

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 Makefile.am                      |   2 +
 lib/backend.py                   |   4 +-
 lib/utils/__init__.py            | 729 +----------------------------
 lib/utils/io.py                  | 762 +++++++++++++++++++++++++++++++
 test/ganeti.utils.io_unittest.py | 694 ++++++++++++++++++++++++++++
 test/ganeti.utils_unittest.py    | 682 +--------------------------
 6 files changed, 1473 insertions(+), 1400 deletions(-)
 create mode 100644 lib/utils/io.py
 create mode 100755 test/ganeti.utils.io_unittest.py

diff --git a/Makefile.am b/Makefile.am
index 7c44969db..9675addf6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -216,6 +216,7 @@ utils_PYTHON = \
 	lib/utils/algo.py \
 	lib/utils/filelock.py \
 	lib/utils/hash.py \
+	lib/utils/io.py \
 	lib/utils/log.py \
 	lib/utils/mlock.py \
 	lib/utils/retry.py \
@@ -489,6 +490,7 @@ python_tests = \
 	test/ganeti.utils.algo_unittest.py \
 	test/ganeti.utils.filelock_unittest.py \
 	test/ganeti.utils.hash_unittest.py \
+	test/ganeti.utils.io_unittest.py \
 	test/ganeti.utils.mlock_unittest.py \
 	test/ganeti.utils.retry_unittest.py \
 	test/ganeti.utils.text_unittest.py \
diff --git a/lib/backend.py b/lib/backend.py
index c79dd9eda..36a44b303 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2512,7 +2512,7 @@ def _EnsureJobQueueFile(file_name):
 def JobQueueUpdate(file_name, content):
   """Updates a file in the queue directory.
 
-  This is just a wrapper over L{utils.WriteFile}, with proper
+  This is just a wrapper over L{utils.io.WriteFile}, with proper
   checking.
 
   @type file_name: str
@@ -3399,7 +3399,7 @@ class DevCacheManager(object):
   def RemoveCache(cls, dev_path):
     """Remove data for a dev_path.
 
-    This is just a wrapper over L{utils.RemoveFile} with a converted
+    This is just a wrapper over L{utils.io.RemoveFile} with a converted
     path name and logging.
 
     @type dev_path: str
diff --git a/lib/utils/__init__.py b/lib/utils/__init__.py
index a7b603341..f13cca359 100644
--- a/lib/utils/__init__.py
+++ b/lib/utils/__init__.py
@@ -61,6 +61,7 @@ from ganeti.utils.log import * # pylint: disable-msg=W0401
 from ganeti.utils.hash import * # pylint: disable-msg=W0401
 from ganeti.utils.wrapper import * # pylint: disable-msg=W0401
 from ganeti.utils.filelock import * # pylint: disable-msg=W0401
+from ganeti.utils.io import * # pylint: disable-msg=W0401
 
 
 #: when set to True, L{RunCmd} is disabled
@@ -701,85 +702,6 @@ def RunParts(dir_name, env=None, reset_env=False):
   return rr
 
 
-def RemoveFile(filename):
-  """Remove a file ignoring some errors.
-
-  Remove a file, ignoring non-existing ones or directories. Other
-  errors are passed.
-
-  @type filename: str
-  @param filename: the file to be removed
-
-  """
-  try:
-    os.unlink(filename)
-  except OSError, err:
-    if err.errno not in (errno.ENOENT, errno.EISDIR):
-      raise
-
-
-def RemoveDir(dirname):
-  """Remove an empty directory.
-
-  Remove a directory, ignoring non-existing ones.
-  Other errors are passed. This includes the case,
-  where the directory is not empty, so it can't be removed.
-
-  @type dirname: str
-  @param dirname: the empty directory to be removed
-
-  """
-  try:
-    os.rmdir(dirname)
-  except OSError, err:
-    if err.errno != errno.ENOENT:
-      raise
-
-
-def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
-  """Renames a file.
-
-  @type old: string
-  @param old: Original path
-  @type new: string
-  @param new: New path
-  @type mkdir: bool
-  @param mkdir: Whether to create target directory if it doesn't exist
-  @type mkdir_mode: int
-  @param mkdir_mode: Mode for newly created directories
-
-  """
-  try:
-    return os.rename(old, new)
-  except OSError, err:
-    # In at least one use case of this function, the job queue, directory
-    # creation is very rare. Checking for the directory before renaming is not
-    # as efficient.
-    if mkdir and err.errno == errno.ENOENT:
-      # Create directory and try again
-      Makedirs(os.path.dirname(new), mode=mkdir_mode)
-
-      return os.rename(old, new)
-
-    raise
-
-
-def Makedirs(path, mode=0750):
-  """Super-mkdir; create a leaf directory and all intermediate ones.
-
-  This is a wrapper around C{os.makedirs} adding error handling not implemented
-  before Python 2.5.
-
-  """
-  try:
-    os.makedirs(path, mode)
-  except OSError, err:
-    # Ignore EEXIST. This is only handled in os.makedirs as included in
-    # Python 2.5 and above.
-    if err.errno != errno.EEXIST or not os.path.exists(path):
-      raise
-
-
 def ResetTempfileModule():
   """Resets the random name generator of the tempfile module.
 
@@ -1002,63 +924,6 @@ def IsProcessHandlingSignal(pid, signum, status_path=None):
   return signum in _ParseSigsetT(sigcgt)
 
 
-def ReadPidFile(pidfile):
-  """Read a pid from a file.
-
-  @type  pidfile: string
-  @param pidfile: path to the file containing the pid
-  @rtype: int
-  @return: The process id, if the file exists and contains a valid PID,
-           otherwise 0
-
-  """
-  try:
-    raw_data = ReadOneLineFile(pidfile)
-  except EnvironmentError, err:
-    if err.errno != errno.ENOENT:
-      logging.exception("Can't read pid file")
-    return 0
-
-  try:
-    pid = int(raw_data)
-  except (TypeError, ValueError), err:
-    logging.info("Can't parse pid file contents", exc_info=True)
-    return 0
-
-  return pid
-
-
-def ReadLockedPidFile(path):
-  """Reads a locked PID file.
-
-  This can be used together with L{StartDaemon}.
-
-  @type path: string
-  @param path: Path to PID file
-  @return: PID as integer or, if file was unlocked or couldn't be opened, None
-
-  """
-  try:
-    fd = os.open(path, os.O_RDONLY)
-  except EnvironmentError, err:
-    if err.errno == errno.ENOENT:
-      # PID file doesn't exist
-      return None
-    raise
-
-  try:
-    try:
-      # Try to acquire lock
-      LockFile(fd)
-    except errors.LockError:
-      # Couldn't lock, daemon is running
-      return int(os.read(fd, 100))
-  finally:
-    os.close(fd)
-
-  return None
-
-
 def ValidateServiceName(name):
   """Validate the given service name.
 
@@ -1225,72 +1090,6 @@ def ParseCpuMask(cpu_mask):
   return cpu_list
 
 
-def AddAuthorizedKey(file_obj, key):
-  """Adds an SSH public key to an authorized_keys file.
-
-  @type file_obj: str or file handle
-  @param file_obj: path to authorized_keys file
-  @type key: str
-  @param key: string containing key
-
-  """
-  key_fields = key.split()
-
-  if isinstance(file_obj, basestring):
-    f = open(file_obj, 'a+')
-  else:
-    f = file_obj
-
-  try:
-    nl = True
-    for line in f:
-      # Ignore whitespace changes
-      if line.split() == key_fields:
-        break
-      nl = line.endswith('\n')
-    else:
-      if not nl:
-        f.write("\n")
-      f.write(key.rstrip('\r\n'))
-      f.write("\n")
-      f.flush()
-  finally:
-    f.close()
-
-
-def RemoveAuthorizedKey(file_name, key):
-  """Removes an SSH public key from an authorized_keys file.
-
-  @type file_name: str
-  @param file_name: path to authorized_keys file
-  @type key: str
-  @param key: string containing key
-
-  """
-  key_fields = key.split()
-
-  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
-  try:
-    out = os.fdopen(fd, 'w')
-    try:
-      f = open(file_name, 'r')
-      try:
-        for line in f:
-          # Ignore whitespace changes while comparing lines
-          if line.split() != key_fields:
-            out.write(line)
-
-        out.flush()
-        os.rename(tmpname, file_name)
-      finally:
-        f.close()
-    finally:
-      out.close()
-  except:
-    RemoveFile(tmpname)
-    raise
-
-
 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
   """Sets the name of an IP address and hostname in /etc/hosts.
 
@@ -1391,66 +1190,6 @@ def RemoveHostFromEtcHosts(hostname):
   RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
 
 
-def TimestampForFilename():
-  """Returns the current time formatted for filenames.
-
-  The format doesn't contain colons as some shells and applications treat them
-  as separators. Uses the local timezone.
-
-  """
-  return time.strftime("%Y-%m-%d_%H_%M_%S")
-
-
-def CreateBackup(file_name):
-  """Creates a backup of a file.
-
-  @type file_name: str
-  @param file_name: file to be backed up
-  @rtype: str
-  @return: the path to the newly created backup
-  @raise errors.ProgrammerError: for invalid file names
-
-  """
-  if not os.path.isfile(file_name):
-    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
-                                file_name)
-
-  prefix = ("%s.backup-%s." %
-            (os.path.basename(file_name), TimestampForFilename()))
-  dir_name = os.path.dirname(file_name)
-
-  fsrc = open(file_name, 'rb')
-  try:
-    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
-    fdst = os.fdopen(fd, 'wb')
-    try:
-      logging.debug("Backing up %s at %s", file_name, backup_name)
-      shutil.copyfileobj(fsrc, fdst)
-    finally:
-      fdst.close()
-  finally:
-    fsrc.close()
-
-  return backup_name
-
-
-def ListVisibleFiles(path):
-  """Returns a list of visible files in a directory.
-
-  @type path: str
-  @param path: the directory to enumerate
-  @rtype: list
-  @return: the list of all files not starting with a dot
-  @raise ProgrammerError: if L{path} is not an absolue and normalized path
-
-  """
-  if not IsNormAbsPath(path):
-    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
-                                 " absolute/normalized: '%s'" % path)
-  files = [i for i in os.listdir(path) if not i.startswith(".")]
-  return files
-
-
 def GetHomeDir(user, default=None):
   """Try to get the homedir of the given user.
 
@@ -1483,227 +1222,6 @@ def NewUUID():
   return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
 
 
-def EnsureDirs(dirs):
-  """Make required directories, if they don't exist.
-
-  @param dirs: list of tuples (dir_name, dir_mode)
-  @type dirs: list of (string, integer)
-
-  """
-  for dir_name, dir_mode in dirs:
-    try:
-      os.mkdir(dir_name, dir_mode)
-    except EnvironmentError, err:
-      if err.errno != errno.EEXIST:
-        raise errors.GenericError("Cannot create needed directory"
-                                  " '%s': %s" % (dir_name, err))
-    try:
-      os.chmod(dir_name, dir_mode)
-    except EnvironmentError, err:
-      raise errors.GenericError("Cannot change directory permissions on"
-                                " '%s': %s" % (dir_name, err))
-    if not os.path.isdir(dir_name):
-      raise errors.GenericError("%s is not a directory" % dir_name)
-
-
-def ReadFile(file_name, size=-1):
-  """Reads a file.
-
-  @type size: int
-  @param size: Read at most size bytes (if negative, entire file)
-  @rtype: str
-  @return: the (possibly partial) content of the file
-
-  """
-  f = open(file_name, "r")
-  try:
-    return f.read(size)
-  finally:
-    f.close()
-
-
-def WriteFile(file_name, fn=None, data=None,
-              mode=None, uid=-1, gid=-1,
-              atime=None, mtime=None, close=True,
-              dry_run=False, backup=False,
-              prewrite=None, postwrite=None):
-  """(Over)write a file atomically.
-
-  The file_name and either fn (a function taking one argument, the
-  file descriptor, and which should write the data to it) or data (the
-  contents of the file) must be passed. The other arguments are
-  optional and allow setting the file mode, owner and group, and the
-  mtime/atime of the file.
-
-  If the function doesn't raise an exception, it has succeeded and the
-  target file has the new contents. If the function has raised an
-  exception, an existing target file should be unmodified and the
-  temporary file should be removed.
-
-  @type file_name: str
-  @param file_name: the target filename
-  @type fn: callable
-  @param fn: content writing function, called with
-      file descriptor as parameter
-  @type data: str
-  @param data: contents of the file
-  @type mode: int
-  @param mode: file mode
-  @type uid: int
-  @param uid: the owner of the file
-  @type gid: int
-  @param gid: the group of the file
-  @type atime: int
-  @param atime: a custom access time to be set on the file
-  @type mtime: int
-  @param mtime: a custom modification time to be set on the file
-  @type close: boolean
-  @param close: whether to close file after writing it
-  @type prewrite: callable
-  @param prewrite: function to be called before writing content
-  @type postwrite: callable
-  @param postwrite: function to be called after writing content
-
-  @rtype: None or int
-  @return: None if the 'close' parameter evaluates to True,
-      otherwise the file descriptor
-
-  @raise errors.ProgrammerError: if any of the arguments are not valid
-
-  """
-  if not os.path.isabs(file_name):
-    raise errors.ProgrammerError("Path passed to WriteFile is not"
-                                 " absolute: '%s'" % file_name)
-
-  if [fn, data].count(None) != 1:
-    raise errors.ProgrammerError("fn or data required")
-
-  if [atime, mtime].count(None) == 1:
-    raise errors.ProgrammerError("Both atime and mtime must be either"
-                                 " set or None")
-
-  if backup and not dry_run and os.path.isfile(file_name):
-    CreateBackup(file_name)
-
-  dir_name, base_name = os.path.split(file_name)
-  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
-  do_remove = True
-  # here we need to make sure we remove the temp file, if any error
-  # leaves it in place
-  try:
-    if uid != -1 or gid != -1:
-      os.chown(new_name, uid, gid)
-    if mode:
-      os.chmod(new_name, mode)
-    if callable(prewrite):
-      prewrite(fd)
-    if data is not None:
-      os.write(fd, data)
-    else:
-      fn(fd)
-    if callable(postwrite):
-      postwrite(fd)
-    os.fsync(fd)
-    if atime is not None and mtime is not None:
-      os.utime(new_name, (atime, mtime))
-    if not dry_run:
-      os.rename(new_name, file_name)
-      do_remove = False
-  finally:
-    if close:
-      os.close(fd)
-      result = None
-    else:
-      result = fd
-    if do_remove:
-      RemoveFile(new_name)
-
-  return result
-
-
-def GetFileID(path=None, fd=None):
-  """Returns the file 'id', i.e. the dev/inode and mtime information.
-
-  Either the path to the file or the fd must be given.
-
-  @param path: the file path
-  @param fd: a file descriptor
-  @return: a tuple of (device number, inode number, mtime)
-
-  """
-  if [path, fd].count(None) != 1:
-    raise errors.ProgrammerError("One and only one of fd/path must be given")
-
-  if fd is None:
-    st = os.stat(path)
-  else:
-    st = os.fstat(fd)
-
-  return (st.st_dev, st.st_ino, st.st_mtime)
-
-
-def VerifyFileID(fi_disk, fi_ours):
-  """Verifies that two file IDs are matching.
-
-  Differences in the inode/device are not accepted, but and older
-  timestamp for fi_disk is accepted.
-
-  @param fi_disk: tuple (dev, inode, mtime) representing the actual
-      file data
-  @param fi_ours: tuple (dev, inode, mtime) representing the last
-      written file data
-  @rtype: boolean
-
-  """
-  (d1, i1, m1) = fi_disk
-  (d2, i2, m2) = fi_ours
-
-  return (d1, i1) == (d2, i2) and m1 <= m2
-
-
-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:
-    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 ReadOneLineFile(file_name, strict=False):
-  """Return the first non-empty line from a file.
-
-  @type strict: boolean
-  @param strict: if True, abort if the file has more than one
-      non-empty line
-
-  """
-  file_lines = ReadFile(file_name).splitlines()
-  full_lines = filter(bool, file_lines)
-  if not file_lines or not full_lines:
-    raise errors.GenericError("No data in one-liner file %s" % file_name)
-  elif strict and len(full_lines) > 1:
-    raise errors.GenericError("Too many lines in one-liner file %s" %
-                              file_name)
-  return full_lines[0]
-
-
 def FirstFree(seq, base=0):
   """Returns the first non-existing integer from seq.
 
@@ -1904,19 +1422,6 @@ def Daemonize(logfile):
   return wpipe
 
 
-def DaemonPidFileName(name):
-  """Compute a ganeti pid file absolute path
-
-  @type name: str
-  @param name: the daemon name
-  @rtype: str
-  @return: the full path to the pidfile corresponding to the given
-      daemon name
-
-  """
-  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
-
-
 def EnsureDaemon(name):
   """Check for and start daemon if not alive.
 
@@ -1943,49 +1448,6 @@ def StopDaemon(name):
   return True
 
 
-def WritePidFile(pidfile):
-  """Write the current process pidfile.
-
-  @type pidfile: string
-  @param pidfile: the path to the file to be written
-  @raise errors.LockError: if the pid file already exists and
-      points to a live process
-  @rtype: int
-  @return: the file descriptor of the lock file; do not close this unless
-      you want to unlock the pid file
-
-  """
-  # We don't rename nor truncate the file to not drop locks under
-  # existing processes
-  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
-
-  # Lock the PID file (and fail if not possible to do so). Any code
-  # wanting to send a signal to the daemon should try to lock the PID
-  # file before reading it. If acquiring the lock succeeds, the daemon is
-  # no longer running and the signal should not be sent.
-  LockFile(fd_pidfile)
-
-  os.write(fd_pidfile, "%d\n" % os.getpid())
-
-  return fd_pidfile
-
-
-def RemovePidFile(pidfile):
-  """Remove the current process pidfile.
-
-  Any errors are ignored.
-
-  @type pidfile: string
-  @param pidfile: Path to the file to be removed
-
-  """
-  # TODO: we could check here that the file contains our pid
-  try:
-    RemoveFile(pidfile)
-  except Exception: # pylint: disable-msg=W0703
-    pass
-
-
 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
                 waitpid=False):
   """Kill a process given by its pid.
@@ -2049,40 +1511,6 @@ def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
     _helper(pid, signal.SIGKILL, waitpid)
 
 
-def FindFile(name, search_path, test=os.path.exists):
-  """Look for a filesystem object in a given path.
-
-  This is an abstract method to search for filesystem object (files,
-  dirs) under a given search path.
-
-  @type name: str
-  @param name: the name to look for
-  @type search_path: str
-  @param search_path: location to start at
-  @type test: callable
-  @param test: a function taking one argument that should return True
-      if the a given object is valid; the default value is
-      os.path.exists, causing only existing files to be returned
-  @rtype: str or None
-  @return: full path to the object if found, None otherwise
-
-  """
-  # validate the filename mask
-  if constants.EXT_PLUGIN_MASK.match(name) is None:
-    logging.critical("Invalid value passed for external script name: '%s'",
-                     name)
-    return None
-
-  for dir_name in search_path:
-    # FIXME: investigate switch to PathJoin
-    item_name = os.path.sep.join([dir_name, name])
-    # check the user test and that we're indeed resolving to the given
-    # basename
-    if test(item_name) and os.path.basename(item_name) == name:
-      return item_name
-  return None
-
-
 def CheckVolumeGroupSize(vglist, vgname, minsize):
   """Checks if the volume group list is valid.
 
@@ -2144,71 +1572,6 @@ def MergeTime(timetuple):
   return float(seconds) + (float(microseconds) * 0.000001)
 
 
-def IsNormAbsPath(path):
-  """Check whether a path is absolute and also normalized
-
-  This avoids things like /dir/../../other/path to be valid.
-
-  """
-  return os.path.normpath(path) == path and os.path.isabs(path)
-
-
-def PathJoin(*args):
-  """Safe-join a list of path components.
-
-  Requirements:
-      - the first argument must be an absolute path
-      - no component in the path must have backtracking (e.g. /../),
-        since we check for normalization at the end
-
-  @param args: the path components to be joined
-  @raise ValueError: for invalid paths
-
-  """
-  # ensure we're having at least one path passed in
-  assert args
-  # ensure the first component is an absolute and normalized path name
-  root = args[0]
-  if not IsNormAbsPath(root):
-    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
-  result = os.path.join(*args)
-  # ensure that the whole path is normalized
-  if not IsNormAbsPath(result):
-    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
-  # check that we're still under the original prefix
-  prefix = os.path.commonprefix([root, result])
-  if prefix != root:
-    raise ValueError("Error: path joining resulted in different prefix"
-                     " (%s != %s)" % (prefix, root))
-  return result
-
-
-def TailFile(fname, lines=20):
-  """Return the last lines from a file.
-
-  @note: this function will only read and parse the last 4KB of
-      the file; if the lines are very long, it could be that less
-      than the requested number of lines are returned
-
-  @param fname: the file name
-  @type lines: int
-  @param lines: the (maximum) number of lines to return
-
-  """
-  fd = open(fname, "r")
-  try:
-    fd.seek(0, 2)
-    pos = fd.tell()
-    pos = max(0, pos-4096)
-    fd.seek(pos, 0)
-    raw_data = fd.read()
-  finally:
-    fd.close()
-
-  rows = raw_data.splitlines()
-  return rows[-lines:]
-
-
 def _ParseAsn1Generalizedtime(value):
   """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
 
@@ -2436,37 +1799,6 @@ def FindMatch(data, name):
   return None
 
 
-def BytesToMebibyte(value):
-  """Converts bytes to mebibytes.
-
-  @type value: int
-  @param value: Value in bytes
-  @rtype: int
-  @return: Value in mebibytes
-
-  """
-  return int(round(value / (1024.0 * 1024.0), 0))
-
-
-def CalculateDirectorySize(path):
-  """Calculates the size of a directory recursively.
-
-  @type path: string
-  @param path: Path to directory
-  @rtype: int
-  @return: Size in mebibytes
-
-  """
-  size = 0
-
-  for (curpath, _, files) in os.walk(path):
-    for filename in files:
-      st = os.lstat(PathJoin(curpath, filename))
-      size += st.st_size
-
-  return BytesToMebibyte(size)
-
-
 def GetMounts(filename=constants.PROC_MOUNTS):
   """Returns the list of mounted filesystems.
 
@@ -2487,22 +1819,6 @@ def GetMounts(filename=constants.PROC_MOUNTS):
   return data
 
 
-def GetFilesystemStats(path):
-  """Returns the total and free space on a filesystem.
-
-  @type path: string
-  @param path: Path on filesystem to be examined
-  @rtype: int
-  @return: tuple of (Total space, Free space) in mebibytes
-
-  """
-  st = os.statvfs(path)
-
-  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
-  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
-  return (tsize, fsize)
-
-
 def RunInSeparateProcess(fn, *args):
   """Runs a function in a separate process.
 
@@ -2550,49 +1866,6 @@ def RunInSeparateProcess(fn, *args):
   return bool(exitcode)
 
 
-def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
-  """Reads the watcher pause file.
-
-  @type filename: string
-  @param filename: Path to watcher pause file
-  @type now: None, float or int
-  @param now: Current time as Unix timestamp
-  @type remove_after: int
-  @param remove_after: Remove watcher pause file after specified amount of
-    seconds past the pause end time
-
-  """
-  if now is None:
-    now = time.time()
-
-  try:
-    value = ReadFile(filename)
-  except IOError, err:
-    if err.errno != errno.ENOENT:
-      raise
-    value = None
-
-  if value is not None:
-    try:
-      value = int(value)
-    except ValueError:
-      logging.warning(("Watcher pause file (%s) contains invalid value,"
-                       " removing it"), filename)
-      RemoveFile(filename)
-      value = None
-
-    if value is not None:
-      # Remove file if it's outdated
-      if now > (value + remove_after):
-        RemoveFile(filename)
-        value = None
-
-      elif now > value:
-        value = None
-
-  return value
-
-
 def GenerateSelfSignedX509Cert(common_name, validity):
   """Generates a self-signed X509 certificate.
 
diff --git a/lib/utils/io.py b/lib/utils/io.py
new file mode 100644
index 000000000..eef7bb762
--- /dev/null
+++ b/lib/utils/io.py
@@ -0,0 +1,762 @@
+#
+#
+
+# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+"""Utility functions for I/O.
+
+"""
+
+import os
+import logging
+import shutil
+import tempfile
+import errno
+import time
+
+from ganeti import errors
+from ganeti import constants
+from ganeti.utils import filelock
+
+
+def ReadFile(file_name, size=-1):
+  """Reads a file.
+
+  @type size: int
+  @param size: Read at most size bytes (if negative, entire file)
+  @rtype: str
+  @return: the (possibly partial) content of the file
+
+  """
+  f = open(file_name, "r")
+  try:
+    return f.read(size)
+  finally:
+    f.close()
+
+
+def WriteFile(file_name, fn=None, data=None,
+              mode=None, uid=-1, gid=-1,
+              atime=None, mtime=None, close=True,
+              dry_run=False, backup=False,
+              prewrite=None, postwrite=None):
+  """(Over)write a file atomically.
+
+  The file_name and either fn (a function taking one argument, the
+  file descriptor, and which should write the data to it) or data (the
+  contents of the file) must be passed. The other arguments are
+  optional and allow setting the file mode, owner and group, and the
+  mtime/atime of the file.
+
+  If the function doesn't raise an exception, it has succeeded and the
+  target file has the new contents. If the function has raised an
+  exception, an existing target file should be unmodified and the
+  temporary file should be removed.
+
+  @type file_name: str
+  @param file_name: the target filename
+  @type fn: callable
+  @param fn: content writing function, called with
+      file descriptor as parameter
+  @type data: str
+  @param data: contents of the file
+  @type mode: int
+  @param mode: file mode
+  @type uid: int
+  @param uid: the owner of the file
+  @type gid: int
+  @param gid: the group of the file
+  @type atime: int
+  @param atime: a custom access time to be set on the file
+  @type mtime: int
+  @param mtime: a custom modification time to be set on the file
+  @type close: boolean
+  @param close: whether to close file after writing it
+  @type prewrite: callable
+  @param prewrite: function to be called before writing content
+  @type postwrite: callable
+  @param postwrite: function to be called after writing content
+
+  @rtype: None or int
+  @return: None if the 'close' parameter evaluates to True,
+      otherwise the file descriptor
+
+  @raise errors.ProgrammerError: if any of the arguments are not valid
+
+  """
+  if not os.path.isabs(file_name):
+    raise errors.ProgrammerError("Path passed to WriteFile is not"
+                                 " absolute: '%s'" % file_name)
+
+  if [fn, data].count(None) != 1:
+    raise errors.ProgrammerError("fn or data required")
+
+  if [atime, mtime].count(None) == 1:
+    raise errors.ProgrammerError("Both atime and mtime must be either"
+                                 " set or None")
+
+  if backup and not dry_run and os.path.isfile(file_name):
+    CreateBackup(file_name)
+
+  dir_name, base_name = os.path.split(file_name)
+  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
+  do_remove = True
+  # here we need to make sure we remove the temp file, if any error
+  # leaves it in place
+  try:
+    if uid != -1 or gid != -1:
+      os.chown(new_name, uid, gid)
+    if mode:
+      os.chmod(new_name, mode)
+    if callable(prewrite):
+      prewrite(fd)
+    if data is not None:
+      os.write(fd, data)
+    else:
+      fn(fd)
+    if callable(postwrite):
+      postwrite(fd)
+    os.fsync(fd)
+    if atime is not None and mtime is not None:
+      os.utime(new_name, (atime, mtime))
+    if not dry_run:
+      os.rename(new_name, file_name)
+      do_remove = False
+  finally:
+    if close:
+      os.close(fd)
+      result = None
+    else:
+      result = fd
+    if do_remove:
+      RemoveFile(new_name)
+
+  return result
+
+
+def GetFileID(path=None, fd=None):
+  """Returns the file 'id', i.e. the dev/inode and mtime information.
+
+  Either the path to the file or the fd must be given.
+
+  @param path: the file path
+  @param fd: a file descriptor
+  @return: a tuple of (device number, inode number, mtime)
+
+  """
+  if [path, fd].count(None) != 1:
+    raise errors.ProgrammerError("One and only one of fd/path must be given")
+
+  if fd is None:
+    st = os.stat(path)
+  else:
+    st = os.fstat(fd)
+
+  return (st.st_dev, st.st_ino, st.st_mtime)
+
+
+def VerifyFileID(fi_disk, fi_ours):
+  """Verifies that two file IDs are matching.
+
+  Differences in the inode/device are not accepted, but and older
+  timestamp for fi_disk is accepted.
+
+  @param fi_disk: tuple (dev, inode, mtime) representing the actual
+      file data
+  @param fi_ours: tuple (dev, inode, mtime) representing the last
+      written file data
+  @rtype: boolean
+
+  """
+  (d1, i1, m1) = fi_disk
+  (d2, i2, m2) = fi_ours
+
+  return (d1, i1) == (d2, i2) and m1 <= m2
+
+
+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 ReadOneLineFile(file_name, strict=False):
+  """Return the first non-empty line from a file.
+
+  @type strict: boolean
+  @param strict: if True, abort if the file has more than one
+      non-empty line
+
+  """
+  file_lines = ReadFile(file_name).splitlines()
+  full_lines = filter(bool, file_lines)
+  if not file_lines or not full_lines:
+    raise errors.GenericError("No data in one-liner file %s" % file_name)
+  elif strict and len(full_lines) > 1:
+    raise errors.GenericError("Too many lines in one-liner file %s" %
+                              file_name)
+  return full_lines[0]
+
+
+def RemoveFile(filename):
+  """Remove a file ignoring some errors.
+
+  Remove a file, ignoring non-existing ones or directories. Other
+  errors are passed.
+
+  @type filename: str
+  @param filename: the file to be removed
+
+  """
+  try:
+    os.unlink(filename)
+  except OSError, err:
+    if err.errno not in (errno.ENOENT, errno.EISDIR):
+      raise
+
+
+def RemoveDir(dirname):
+  """Remove an empty directory.
+
+  Remove a directory, ignoring non-existing ones.
+  Other errors are passed. This includes the case,
+  where the directory is not empty, so it can't be removed.
+
+  @type dirname: str
+  @param dirname: the empty directory to be removed
+
+  """
+  try:
+    os.rmdir(dirname)
+  except OSError, err:
+    if err.errno != errno.ENOENT:
+      raise
+
+
+def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
+  """Renames a file.
+
+  @type old: string
+  @param old: Original path
+  @type new: string
+  @param new: New path
+  @type mkdir: bool
+  @param mkdir: Whether to create target directory if it doesn't exist
+  @type mkdir_mode: int
+  @param mkdir_mode: Mode for newly created directories
+
+  """
+  try:
+    return os.rename(old, new)
+  except OSError, err:
+    # In at least one use case of this function, the job queue, directory
+    # creation is very rare. Checking for the directory before renaming is not
+    # as efficient.
+    if mkdir and err.errno == errno.ENOENT:
+      # Create directory and try again
+      Makedirs(os.path.dirname(new), mode=mkdir_mode)
+
+      return os.rename(old, new)
+
+    raise
+
+
+def Makedirs(path, mode=0750):
+  """Super-mkdir; create a leaf directory and all intermediate ones.
+
+  This is a wrapper around C{os.makedirs} adding error handling not implemented
+  before Python 2.5.
+
+  """
+  try:
+    os.makedirs(path, mode)
+  except OSError, err:
+    # Ignore EEXIST. This is only handled in os.makedirs as included in
+    # Python 2.5 and above.
+    if err.errno != errno.EEXIST or not os.path.exists(path):
+      raise
+
+
+def TimestampForFilename():
+  """Returns the current time formatted for filenames.
+
+  The format doesn't contain colons as some shells and applications treat them
+  as separators. Uses the local timezone.
+
+  """
+  return time.strftime("%Y-%m-%d_%H_%M_%S")
+
+
+def CreateBackup(file_name):
+  """Creates a backup of a file.
+
+  @type file_name: str
+  @param file_name: file to be backed up
+  @rtype: str
+  @return: the path to the newly created backup
+  @raise errors.ProgrammerError: for invalid file names
+
+  """
+  if not os.path.isfile(file_name):
+    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
+                                file_name)
+
+  prefix = ("%s.backup-%s." %
+            (os.path.basename(file_name), TimestampForFilename()))
+  dir_name = os.path.dirname(file_name)
+
+  fsrc = open(file_name, 'rb')
+  try:
+    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
+    fdst = os.fdopen(fd, 'wb')
+    try:
+      logging.debug("Backing up %s at %s", file_name, backup_name)
+      shutil.copyfileobj(fsrc, fdst)
+    finally:
+      fdst.close()
+  finally:
+    fsrc.close()
+
+  return backup_name
+
+
+def ListVisibleFiles(path):
+  """Returns a list of visible files in a directory.
+
+  @type path: str
+  @param path: the directory to enumerate
+  @rtype: list
+  @return: the list of all files not starting with a dot
+  @raise ProgrammerError: if L{path} is not an absolue and normalized path
+
+  """
+  if not IsNormAbsPath(path):
+    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
+                                 " absolute/normalized: '%s'" % path)
+  files = [i for i in os.listdir(path) if not i.startswith(".")]
+  return files
+
+
+def EnsureDirs(dirs):
+  """Make required directories, if they don't exist.
+
+  @param dirs: list of tuples (dir_name, dir_mode)
+  @type dirs: list of (string, integer)
+
+  """
+  for dir_name, dir_mode in dirs:
+    try:
+      os.mkdir(dir_name, dir_mode)
+    except EnvironmentError, err:
+      if err.errno != errno.EEXIST:
+        raise errors.GenericError("Cannot create needed directory"
+                                  " '%s': %s" % (dir_name, err))
+    try:
+      os.chmod(dir_name, dir_mode)
+    except EnvironmentError, err:
+      raise errors.GenericError("Cannot change directory permissions on"
+                                " '%s': %s" % (dir_name, err))
+    if not os.path.isdir(dir_name):
+      raise errors.GenericError("%s is not a directory" % dir_name)
+
+
+def FindFile(name, search_path, test=os.path.exists):
+  """Look for a filesystem object in a given path.
+
+  This is an abstract method to search for filesystem object (files,
+  dirs) under a given search path.
+
+  @type name: str
+  @param name: the name to look for
+  @type search_path: str
+  @param search_path: location to start at
+  @type test: callable
+  @param test: a function taking one argument that should return True
+      if the a given object is valid; the default value is
+      os.path.exists, causing only existing files to be returned
+  @rtype: str or None
+  @return: full path to the object if found, None otherwise
+
+  """
+  # validate the filename mask
+  if constants.EXT_PLUGIN_MASK.match(name) is None:
+    logging.critical("Invalid value passed for external script name: '%s'",
+                     name)
+    return None
+
+  for dir_name in search_path:
+    # FIXME: investigate switch to PathJoin
+    item_name = os.path.sep.join([dir_name, name])
+    # check the user test and that we're indeed resolving to the given
+    # basename
+    if test(item_name) and os.path.basename(item_name) == name:
+      return item_name
+  return None
+
+
+def IsNormAbsPath(path):
+  """Check whether a path is absolute and also normalized
+
+  This avoids things like /dir/../../other/path to be valid.
+
+  """
+  return os.path.normpath(path) == path and os.path.isabs(path)
+
+
+def PathJoin(*args):
+  """Safe-join a list of path components.
+
+  Requirements:
+      - the first argument must be an absolute path
+      - no component in the path must have backtracking (e.g. /../),
+        since we check for normalization at the end
+
+  @param args: the path components to be joined
+  @raise ValueError: for invalid paths
+
+  """
+  # ensure we're having at least one path passed in
+  assert args
+  # ensure the first component is an absolute and normalized path name
+  root = args[0]
+  if not IsNormAbsPath(root):
+    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
+  result = os.path.join(*args)
+  # ensure that the whole path is normalized
+  if not IsNormAbsPath(result):
+    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
+  # check that we're still under the original prefix
+  prefix = os.path.commonprefix([root, result])
+  if prefix != root:
+    raise ValueError("Error: path joining resulted in different prefix"
+                     " (%s != %s)" % (prefix, root))
+  return result
+
+
+def TailFile(fname, lines=20):
+  """Return the last lines from a file.
+
+  @note: this function will only read and parse the last 4KB of
+      the file; if the lines are very long, it could be that less
+      than the requested number of lines are returned
+
+  @param fname: the file name
+  @type lines: int
+  @param lines: the (maximum) number of lines to return
+
+  """
+  fd = open(fname, "r")
+  try:
+    fd.seek(0, 2)
+    pos = fd.tell()
+    pos = max(0, pos-4096)
+    fd.seek(pos, 0)
+    raw_data = fd.read()
+  finally:
+    fd.close()
+
+  rows = raw_data.splitlines()
+  return rows[-lines:]
+
+
+def BytesToMebibyte(value):
+  """Converts bytes to mebibytes.
+
+  @type value: int
+  @param value: Value in bytes
+  @rtype: int
+  @return: Value in mebibytes
+
+  """
+  return int(round(value / (1024.0 * 1024.0), 0))
+
+
+def CalculateDirectorySize(path):
+  """Calculates the size of a directory recursively.
+
+  @type path: string
+  @param path: Path to directory
+  @rtype: int
+  @return: Size in mebibytes
+
+  """
+  size = 0
+
+  for (curpath, _, files) in os.walk(path):
+    for filename in files:
+      st = os.lstat(PathJoin(curpath, filename))
+      size += st.st_size
+
+  return BytesToMebibyte(size)
+
+
+def GetFilesystemStats(path):
+  """Returns the total and free space on a filesystem.
+
+  @type path: string
+  @param path: Path on filesystem to be examined
+  @rtype: int
+  @return: tuple of (Total space, Free space) in mebibytes
+
+  """
+  st = os.statvfs(path)
+
+  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
+  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
+  return (tsize, fsize)
+
+
+def ReadPidFile(pidfile):
+  """Read a pid from a file.
+
+  @type  pidfile: string
+  @param pidfile: path to the file containing the pid
+  @rtype: int
+  @return: The process id, if the file exists and contains a valid PID,
+           otherwise 0
+
+  """
+  try:
+    raw_data = ReadOneLineFile(pidfile)
+  except EnvironmentError, err:
+    if err.errno != errno.ENOENT:
+      logging.exception("Can't read pid file")
+    return 0
+
+  try:
+    pid = int(raw_data)
+  except (TypeError, ValueError), err:
+    logging.info("Can't parse pid file contents", exc_info=True)
+    return 0
+
+  return pid
+
+
+def ReadLockedPidFile(path):
+  """Reads a locked PID file.
+
+  This can be used together with L{utils.StartDaemon}.
+
+  @type path: string
+  @param path: Path to PID file
+  @return: PID as integer or, if file was unlocked or couldn't be opened, None
+
+  """
+  try:
+    fd = os.open(path, os.O_RDONLY)
+  except EnvironmentError, err:
+    if err.errno == errno.ENOENT:
+      # PID file doesn't exist
+      return None
+    raise
+
+  try:
+    try:
+      # Try to acquire lock
+      filelock.LockFile(fd)
+    except errors.LockError:
+      # Couldn't lock, daemon is running
+      return int(os.read(fd, 100))
+  finally:
+    os.close(fd)
+
+  return None
+
+
+def AddAuthorizedKey(file_obj, key):
+  """Adds an SSH public key to an authorized_keys file.
+
+  @type file_obj: str or file handle
+  @param file_obj: path to authorized_keys file
+  @type key: str
+  @param key: string containing key
+
+  """
+  key_fields = key.split()
+
+  if isinstance(file_obj, basestring):
+    f = open(file_obj, 'a+')
+  else:
+    f = file_obj
+
+  try:
+    nl = True
+    for line in f:
+      # Ignore whitespace changes
+      if line.split() == key_fields:
+        break
+      nl = line.endswith('\n')
+    else:
+      if not nl:
+        f.write("\n")
+      f.write(key.rstrip('\r\n'))
+      f.write("\n")
+      f.flush()
+  finally:
+    f.close()
+
+
+def RemoveAuthorizedKey(file_name, key):
+  """Removes an SSH public key from an authorized_keys file.
+
+  @type file_name: str
+  @param file_name: path to authorized_keys file
+  @type key: str
+  @param key: string containing key
+
+  """
+  key_fields = key.split()
+
+  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
+  try:
+    out = os.fdopen(fd, 'w')
+    try:
+      f = open(file_name, 'r')
+      try:
+        for line in f:
+          # Ignore whitespace changes while comparing lines
+          if line.split() != key_fields:
+            out.write(line)
+
+        out.flush()
+        os.rename(tmpname, file_name)
+      finally:
+        f.close()
+    finally:
+      out.close()
+  except:
+    RemoveFile(tmpname)
+    raise
+
+
+def DaemonPidFileName(name):
+  """Compute a ganeti pid file absolute path
+
+  @type name: str
+  @param name: the daemon name
+  @rtype: str
+  @return: the full path to the pidfile corresponding to the given
+      daemon name
+
+  """
+  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
+
+
+def WritePidFile(pidfile):
+  """Write the current process pidfile.
+
+  @type pidfile: string
+  @param pidfile: the path to the file to be written
+  @raise errors.LockError: if the pid file already exists and
+      points to a live process
+  @rtype: int
+  @return: the file descriptor of the lock file; do not close this unless
+      you want to unlock the pid file
+
+  """
+  # We don't rename nor truncate the file to not drop locks under
+  # existing processes
+  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
+
+  # Lock the PID file (and fail if not possible to do so). Any code
+  # wanting to send a signal to the daemon should try to lock the PID
+  # file before reading it. If acquiring the lock succeeds, the daemon is
+  # no longer running and the signal should not be sent.
+  filelock.LockFile(fd_pidfile)
+
+  os.write(fd_pidfile, "%d\n" % os.getpid())
+
+  return fd_pidfile
+
+
+def RemovePidFile(pidfile):
+  """Remove the current process pidfile.
+
+  Any errors are ignored.
+
+  @type pidfile: string
+  @param pidfile: Path to the file to be removed
+
+  """
+  # TODO: we could check here that the file contains our pid
+  try:
+    RemoveFile(pidfile)
+  except Exception: # pylint: disable-msg=W0703
+    pass
+
+
+def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
+  """Reads the watcher pause file.
+
+  @type filename: string
+  @param filename: Path to watcher pause file
+  @type now: None, float or int
+  @param now: Current time as Unix timestamp
+  @type remove_after: int
+  @param remove_after: Remove watcher pause file after specified amount of
+    seconds past the pause end time
+
+  """
+  if now is None:
+    now = time.time()
+
+  try:
+    value = ReadFile(filename)
+  except IOError, err:
+    if err.errno != errno.ENOENT:
+      raise
+    value = None
+
+  if value is not None:
+    try:
+      value = int(value)
+    except ValueError:
+      logging.warning(("Watcher pause file (%s) contains invalid value,"
+                       " removing it"), filename)
+      RemoveFile(filename)
+      value = None
+
+    if value is not None:
+      # Remove file if it's outdated
+      if now > (value + remove_after):
+        RemoveFile(filename)
+        value = None
+
+      elif now > value:
+        value = None
+
+  return value
diff --git a/test/ganeti.utils.io_unittest.py b/test/ganeti.utils.io_unittest.py
new file mode 100755
index 000000000..b83b51e52
--- /dev/null
+++ b/test/ganeti.utils.io_unittest.py
@@ -0,0 +1,694 @@
+#!/usr/bin/python
+#
+
+# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+
+"""Script for testing ganeti.utils.io"""
+
+import os
+import tempfile
+import unittest
+import shutil
+import glob
+import time
+
+from ganeti import constants
+from ganeti import utils
+from ganeti import compat
+from ganeti import errors
+
+import testutils
+
+
+class TestReadFile(testutils.GanetiTestCase):
+  def testReadAll(self):
+    data = utils.ReadFile(self._TestDataFilename("cert1.pem"))
+    self.assertEqual(len(data), 814)
+
+    h = compat.md5_hash()
+    h.update(data)
+    self.assertEqual(h.hexdigest(), "a491efb3efe56a0535f924d5f8680fd4")
+
+  def testReadSize(self):
+    data = utils.ReadFile(self._TestDataFilename("cert1.pem"),
+                          size=100)
+    self.assertEqual(len(data), 100)
+
+    h = compat.md5_hash()
+    h.update(data)
+    self.assertEqual(h.hexdigest(), "893772354e4e690b9efd073eed433ce7")
+
+  def testError(self):
+    self.assertRaises(EnvironmentError, utils.ReadFile,
+                      "/dev/null/does-not-exist")
+
+
+class TestReadOneLineFile(testutils.GanetiTestCase):
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+
+  def testDefault(self):
+    data = utils.ReadOneLineFile(self._TestDataFilename("cert1.pem"))
+    self.assertEqual(len(data), 27)
+    self.assertEqual(data, "-----BEGIN CERTIFICATE-----")
+
+  def testNotStrict(self):
+    data = utils.ReadOneLineFile(self._TestDataFilename("cert1.pem"),
+                                 strict=False)
+    self.assertEqual(len(data), 27)
+    self.assertEqual(data, "-----BEGIN CERTIFICATE-----")
+
+  def testStrictFailure(self):
+    self.assertRaises(errors.GenericError, utils.ReadOneLineFile,
+                      self._TestDataFilename("cert1.pem"), strict=True)
+
+  def testLongLine(self):
+    dummydata = (1024 * "Hello World! ")
+    myfile = self._CreateTempFile()
+    utils.WriteFile(myfile, data=dummydata)
+    datastrict = utils.ReadOneLineFile(myfile, strict=True)
+    datalax = utils.ReadOneLineFile(myfile, strict=False)
+    self.assertEqual(dummydata, datastrict)
+    self.assertEqual(dummydata, datalax)
+
+  def testNewline(self):
+    myfile = self._CreateTempFile()
+    myline = "myline"
+    for nl in ["", "\n", "\r\n"]:
+      dummydata = "%s%s" % (myline, nl)
+      utils.WriteFile(myfile, data=dummydata)
+      datalax = utils.ReadOneLineFile(myfile, strict=False)
+      self.assertEqual(myline, datalax)
+      datastrict = utils.ReadOneLineFile(myfile, strict=True)
+      self.assertEqual(myline, datastrict)
+
+  def testWhitespaceAndMultipleLines(self):
+    myfile = self._CreateTempFile()
+    for nl in ["", "\n", "\r\n"]:
+      for ws in [" ", "\t", "\t\t  \t", "\t "]:
+        dummydata = (1024 * ("Foo bar baz %s%s" % (ws, nl)))
+        utils.WriteFile(myfile, data=dummydata)
+        datalax = utils.ReadOneLineFile(myfile, strict=False)
+        if nl:
+          self.assert_(set("\r\n") & set(dummydata))
+          self.assertRaises(errors.GenericError, utils.ReadOneLineFile,
+                            myfile, strict=True)
+          explen = len("Foo bar baz ") + len(ws)
+          self.assertEqual(len(datalax), explen)
+          self.assertEqual(datalax, dummydata[:explen])
+          self.assertFalse(set("\r\n") & set(datalax))
+        else:
+          datastrict = utils.ReadOneLineFile(myfile, strict=True)
+          self.assertEqual(dummydata, datastrict)
+          self.assertEqual(dummydata, datalax)
+
+  def testEmptylines(self):
+    myfile = self._CreateTempFile()
+    myline = "myline"
+    for nl in ["\n", "\r\n"]:
+      for ol in ["", "otherline"]:
+        dummydata = "%s%s%s%s%s%s" % (nl, nl, myline, nl, ol, nl)
+        utils.WriteFile(myfile, data=dummydata)
+        self.assert_(set("\r\n") & set(dummydata))
+        datalax = utils.ReadOneLineFile(myfile, strict=False)
+        self.assertEqual(myline, datalax)
+        if ol:
+          self.assertRaises(errors.GenericError, utils.ReadOneLineFile,
+                            myfile, strict=True)
+        else:
+          datastrict = utils.ReadOneLineFile(myfile, strict=True)
+          self.assertEqual(myline, datastrict)
+
+  def testEmptyfile(self):
+    myfile = self._CreateTempFile()
+    self.assertRaises(errors.GenericError, utils.ReadOneLineFile, myfile)
+
+
+class TestTimestampForFilename(unittest.TestCase):
+  def test(self):
+    self.assert_("." not in utils.TimestampForFilename())
+    self.assert_(":" not in utils.TimestampForFilename())
+
+
+class TestCreateBackup(testutils.GanetiTestCase):
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    testutils.GanetiTestCase.tearDown(self)
+
+    shutil.rmtree(self.tmpdir)
+
+  def testEmpty(self):
+    filename = utils.PathJoin(self.tmpdir, "config.data")
+    utils.WriteFile(filename, data="")
+    bname = utils.CreateBackup(filename)
+    self.assertFileContent(bname, "")
+    self.assertEqual(len(glob.glob("%s*" % filename)), 2)
+    utils.CreateBackup(filename)
+    self.assertEqual(len(glob.glob("%s*" % filename)), 3)
+    utils.CreateBackup(filename)
+    self.assertEqual(len(glob.glob("%s*" % filename)), 4)
+
+    fifoname = utils.PathJoin(self.tmpdir, "fifo")
+    os.mkfifo(fifoname)
+    self.assertRaises(errors.ProgrammerError, utils.CreateBackup, fifoname)
+
+  def testContent(self):
+    bkpcount = 0
+    for data in ["", "X", "Hello World!\n" * 100, "Binary data\0\x01\x02\n"]:
+      for rep in [1, 2, 10, 127]:
+        testdata = data * rep
+
+        filename = utils.PathJoin(self.tmpdir, "test.data_")
+        utils.WriteFile(filename, data=testdata)
+        self.assertFileContent(filename, testdata)
+
+        for _ in range(3):
+          bname = utils.CreateBackup(filename)
+          bkpcount += 1
+          self.assertFileContent(bname, testdata)
+          self.assertEqual(len(glob.glob("%s*" % filename)), 1 + bkpcount)
+
+
+class TestListVisibleFiles(unittest.TestCase):
+  """Test case for ListVisibleFiles"""
+
+  def setUp(self):
+    self.path = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.path)
+
+  def _CreateFiles(self, files):
+    for name in files:
+      utils.WriteFile(os.path.join(self.path, name), data="test")
+
+  def _test(self, files, expected):
+    self._CreateFiles(files)
+    found = utils.ListVisibleFiles(self.path)
+    self.assertEqual(set(found), set(expected))
+
+  def testAllVisible(self):
+    files = ["a", "b", "c"]
+    expected = files
+    self._test(files, expected)
+
+  def testNoneVisible(self):
+    files = [".a", ".b", ".c"]
+    expected = []
+    self._test(files, expected)
+
+  def testSomeVisible(self):
+    files = ["a", "b", ".c"]
+    expected = ["a", "b"]
+    self._test(files, expected)
+
+  def testNonAbsolutePath(self):
+    self.failUnlessRaises(errors.ProgrammerError, utils.ListVisibleFiles,
+                          "abc")
+
+  def testNonNormalizedPath(self):
+    self.failUnlessRaises(errors.ProgrammerError, utils.ListVisibleFiles,
+                          "/bin/../tmp")
+
+
+class TestWriteFile(unittest.TestCase):
+  def setUp(self):
+    self.tfile = tempfile.NamedTemporaryFile()
+    self.did_pre = False
+    self.did_post = False
+    self.did_write = False
+
+  def markPre(self, fd):
+    self.did_pre = True
+
+  def markPost(self, fd):
+    self.did_post = True
+
+  def markWrite(self, fd):
+    self.did_write = True
+
+  def testWrite(self):
+    data = "abc"
+    utils.WriteFile(self.tfile.name, data=data)
+    self.assertEqual(utils.ReadFile(self.tfile.name), data)
+
+  def testErrors(self):
+    self.assertRaises(errors.ProgrammerError, utils.WriteFile,
+                      self.tfile.name, data="test", fn=lambda fd: None)
+    self.assertRaises(errors.ProgrammerError, utils.WriteFile, self.tfile.name)
+    self.assertRaises(errors.ProgrammerError, utils.WriteFile,
+                      self.tfile.name, data="test", atime=0)
+
+  def testCalls(self):
+    utils.WriteFile(self.tfile.name, fn=self.markWrite,
+                    prewrite=self.markPre, postwrite=self.markPost)
+    self.assertTrue(self.did_pre)
+    self.assertTrue(self.did_post)
+    self.assertTrue(self.did_write)
+
+  def testDryRun(self):
+    orig = "abc"
+    self.tfile.write(orig)
+    self.tfile.flush()
+    utils.WriteFile(self.tfile.name, data="hello", dry_run=True)
+    self.assertEqual(utils.ReadFile(self.tfile.name), orig)
+
+  def testTimes(self):
+    f = self.tfile.name
+    for at, mt in [(0, 0), (1000, 1000), (2000, 3000),
+                   (int(time.time()), 5000)]:
+      utils.WriteFile(f, data="hello", atime=at, mtime=mt)
+      st = os.stat(f)
+      self.assertEqual(st.st_atime, at)
+      self.assertEqual(st.st_mtime, mt)
+
+  def testNoClose(self):
+    data = "hello"
+    self.assertEqual(utils.WriteFile(self.tfile.name, data="abc"), None)
+    fd = utils.WriteFile(self.tfile.name, data=data, close=False)
+    try:
+      os.lseek(fd, 0, 0)
+      self.assertEqual(os.read(fd, 4096), data)
+    finally:
+      os.close(fd)
+
+
+class TestFileID(testutils.GanetiTestCase):
+  def testEquality(self):
+    name = self._CreateTempFile()
+    oldi = utils.GetFileID(path=name)
+    self.failUnless(utils.VerifyFileID(oldi, oldi))
+
+  def testUpdate(self):
+    name = self._CreateTempFile()
+    oldi = utils.GetFileID(path=name)
+    os.utime(name, None)
+    fd = os.open(name, os.O_RDWR)
+    try:
+      newi = utils.GetFileID(fd=fd)
+      self.failUnless(utils.VerifyFileID(oldi, newi))
+      self.failUnless(utils.VerifyFileID(newi, oldi))
+    finally:
+      os.close(fd)
+
+  def testWriteFile(self):
+    name = self._CreateTempFile()
+    oldi = utils.GetFileID(path=name)
+    mtime = oldi[2]
+    os.utime(name, (mtime + 10, mtime + 10))
+    self.assertRaises(errors.LockError, utils.SafeWriteFile, name,
+                      oldi, data="")
+    os.utime(name, (mtime - 10, mtime - 10))
+    utils.SafeWriteFile(name, oldi, data="")
+    oldi = utils.GetFileID(path=name)
+    mtime = oldi[2]
+    os.utime(name, (mtime + 10, mtime + 10))
+    # this doesn't raise, since we passed None
+    utils.SafeWriteFile(name, None, data="")
+
+  def testError(self):
+    t = tempfile.NamedTemporaryFile()
+    self.assertRaises(errors.ProgrammerError, utils.GetFileID,
+                      path=t.name, fd=t.fileno())
+
+
+class TestRemoveFile(unittest.TestCase):
+  """Test case for the RemoveFile function"""
+
+  def setUp(self):
+    """Create a temp dir and file for each case"""
+    self.tmpdir = tempfile.mkdtemp('', 'ganeti-unittest-')
+    fd, self.tmpfile = tempfile.mkstemp('', '', self.tmpdir)
+    os.close(fd)
+
+  def tearDown(self):
+    if os.path.exists(self.tmpfile):
+      os.unlink(self.tmpfile)
+    os.rmdir(self.tmpdir)
+
+  def testIgnoreDirs(self):
+    """Test that RemoveFile() ignores directories"""
+    self.assertEqual(None, utils.RemoveFile(self.tmpdir))
+
+  def testIgnoreNotExisting(self):
+    """Test that RemoveFile() ignores non-existing files"""
+    utils.RemoveFile(self.tmpfile)
+    utils.RemoveFile(self.tmpfile)
+
+  def testRemoveFile(self):
+    """Test that RemoveFile does remove a file"""
+    utils.RemoveFile(self.tmpfile)
+    if os.path.exists(self.tmpfile):
+      self.fail("File '%s' not removed" % self.tmpfile)
+
+  def testRemoveSymlink(self):
+    """Test that RemoveFile does remove symlinks"""
+    symlink = self.tmpdir + "/symlink"
+    os.symlink("no-such-file", symlink)
+    utils.RemoveFile(symlink)
+    if os.path.exists(symlink):
+      self.fail("File '%s' not removed" % symlink)
+    os.symlink(self.tmpfile, symlink)
+    utils.RemoveFile(symlink)
+    if os.path.exists(symlink):
+      self.fail("File '%s' not removed" % symlink)
+
+
+class TestRemoveDir(unittest.TestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    try:
+      shutil.rmtree(self.tmpdir)
+    except EnvironmentError:
+      pass
+
+  def testEmptyDir(self):
+    utils.RemoveDir(self.tmpdir)
+    self.assertFalse(os.path.isdir(self.tmpdir))
+
+  def testNonEmptyDir(self):
+    self.tmpfile = os.path.join(self.tmpdir, "test1")
+    open(self.tmpfile, "w").close()
+    self.assertRaises(EnvironmentError, utils.RemoveDir, self.tmpdir)
+
+
+class TestRename(unittest.TestCase):
+  """Test case for RenameFile"""
+
+  def setUp(self):
+    """Create a temporary directory"""
+    self.tmpdir = tempfile.mkdtemp()
+    self.tmpfile = os.path.join(self.tmpdir, "test1")
+
+    # Touch the file
+    open(self.tmpfile, "w").close()
+
+  def tearDown(self):
+    """Remove temporary directory"""
+    shutil.rmtree(self.tmpdir)
+
+  def testSimpleRename1(self):
+    """Simple rename 1"""
+    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "xyz"))
+    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "xyz")))
+
+  def testSimpleRename2(self):
+    """Simple rename 2"""
+    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "xyz"),
+                     mkdir=True)
+    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "xyz")))
+
+  def testRenameMkdir(self):
+    """Rename with mkdir"""
+    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "test/xyz"),
+                     mkdir=True)
+    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test")))
+    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "test/xyz")))
+
+    utils.RenameFile(os.path.join(self.tmpdir, "test/xyz"),
+                     os.path.join(self.tmpdir, "test/foo/bar/baz"),
+                     mkdir=True)
+    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test")))
+    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test/foo/bar")))
+    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "test/foo/bar/baz")))
+
+
+class TestMakedirs(unittest.TestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def testNonExisting(self):
+    path = utils.PathJoin(self.tmpdir, "foo")
+    utils.Makedirs(path)
+    self.assert_(os.path.isdir(path))
+
+  def testExisting(self):
+    path = utils.PathJoin(self.tmpdir, "foo")
+    os.mkdir(path)
+    utils.Makedirs(path)
+    self.assert_(os.path.isdir(path))
+
+  def testRecursiveNonExisting(self):
+    path = utils.PathJoin(self.tmpdir, "foo/bar/baz")
+    utils.Makedirs(path)
+    self.assert_(os.path.isdir(path))
+
+  def testRecursiveExisting(self):
+    path = utils.PathJoin(self.tmpdir, "B/moo/xyz")
+    self.assertFalse(os.path.exists(path))
+    os.mkdir(utils.PathJoin(self.tmpdir, "B"))
+    utils.Makedirs(path)
+    self.assert_(os.path.isdir(path))
+
+
+class TestEnsureDirs(unittest.TestCase):
+  """Tests for EnsureDirs"""
+
+  def setUp(self):
+    self.dir = tempfile.mkdtemp()
+    self.old_umask = os.umask(0777)
+
+  def testEnsureDirs(self):
+    utils.EnsureDirs([
+        (utils.PathJoin(self.dir, "foo"), 0777),
+        (utils.PathJoin(self.dir, "bar"), 0000),
+        ])
+    self.assertEquals(os.stat(utils.PathJoin(self.dir, "foo"))[0] & 0777, 0777)
+    self.assertEquals(os.stat(utils.PathJoin(self.dir, "bar"))[0] & 0777, 0000)
+
+  def tearDown(self):
+    os.rmdir(utils.PathJoin(self.dir, "foo"))
+    os.rmdir(utils.PathJoin(self.dir, "bar"))
+    os.rmdir(self.dir)
+    os.umask(self.old_umask)
+
+
+class TestIsNormAbsPath(unittest.TestCase):
+  """Testing case for IsNormAbsPath"""
+
+  def _pathTestHelper(self, path, result):
+    if result:
+      self.assert_(utils.IsNormAbsPath(path),
+          "Path %s should result absolute and normalized" % path)
+    else:
+      self.assertFalse(utils.IsNormAbsPath(path),
+          "Path %s should not result absolute and normalized" % path)
+
+  def testBase(self):
+    self._pathTestHelper("/etc", True)
+    self._pathTestHelper("/srv", True)
+    self._pathTestHelper("etc", False)
+    self._pathTestHelper("/etc/../root", False)
+    self._pathTestHelper("/etc/", False)
+
+
+class TestPathJoin(unittest.TestCase):
+  """Testing case for PathJoin"""
+
+  def testBasicItems(self):
+    mlist = ["/a", "b", "c"]
+    self.failUnlessEqual(utils.PathJoin(*mlist), "/".join(mlist))
+
+  def testNonAbsPrefix(self):
+    self.failUnlessRaises(ValueError, utils.PathJoin, "a", "b")
+
+  def testBackTrack(self):
+    self.failUnlessRaises(ValueError, utils.PathJoin, "/a", "b/../c")
+
+  def testMultiAbs(self):
+    self.failUnlessRaises(ValueError, utils.PathJoin, "/a", "/b")
+
+
+class TestTailFile(testutils.GanetiTestCase):
+  """Test case for the TailFile function"""
+
+  def testEmpty(self):
+    fname = self._CreateTempFile()
+    self.failUnlessEqual(utils.TailFile(fname), [])
+    self.failUnlessEqual(utils.TailFile(fname, lines=25), [])
+
+  def testAllLines(self):
+    data = ["test %d" % i for i in range(30)]
+    for i in range(30):
+      fname = self._CreateTempFile()
+      fd = open(fname, "w")
+      fd.write("\n".join(data[:i]))
+      if i > 0:
+        fd.write("\n")
+      fd.close()
+      self.failUnlessEqual(utils.TailFile(fname, lines=i), data[:i])
+
+  def testPartialLines(self):
+    data = ["test %d" % i for i in range(30)]
+    fname = self._CreateTempFile()
+    fd = open(fname, "w")
+    fd.write("\n".join(data))
+    fd.write("\n")
+    fd.close()
+    for i in range(1, 30):
+      self.failUnlessEqual(utils.TailFile(fname, lines=i), data[-i:])
+
+  def testBigFile(self):
+    data = ["test %d" % i for i in range(30)]
+    fname = self._CreateTempFile()
+    fd = open(fname, "w")
+    fd.write("X" * 1048576)
+    fd.write("\n")
+    fd.write("\n".join(data))
+    fd.write("\n")
+    fd.close()
+    for i in range(1, 30):
+      self.failUnlessEqual(utils.TailFile(fname, lines=i), data[-i:])
+
+
+class TestPidFileFunctions(unittest.TestCase):
+  """Tests for WritePidFile, RemovePidFile and ReadPidFile"""
+
+  def setUp(self):
+    self.dir = tempfile.mkdtemp()
+    self.f_dpn = lambda name: os.path.join(self.dir, "%s.pid" % name)
+
+  def testPidFileFunctions(self):
+    pid_file = self.f_dpn('test')
+    fd = utils.WritePidFile(self.f_dpn('test'))
+    self.failUnless(os.path.exists(pid_file),
+                    "PID file should have been created")
+    read_pid = utils.ReadPidFile(pid_file)
+    self.failUnlessEqual(read_pid, os.getpid())
+    self.failUnless(utils.IsProcessAlive(read_pid))
+    self.failUnlessRaises(errors.LockError, utils.WritePidFile,
+                          self.f_dpn('test'))
+    os.close(fd)
+    utils.RemovePidFile(self.f_dpn("test"))
+    self.failIf(os.path.exists(pid_file),
+                "PID file should not exist anymore")
+    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
+                         "ReadPidFile should return 0 for missing pid file")
+    fh = open(pid_file, "w")
+    fh.write("blah\n")
+    fh.close()
+    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
+                         "ReadPidFile should return 0 for invalid pid file")
+    # but now, even with the file existing, we should be able to lock it
+    fd = utils.WritePidFile(self.f_dpn('test'))
+    os.close(fd)
+    utils.RemovePidFile(self.f_dpn("test"))
+    self.failIf(os.path.exists(pid_file),
+                "PID file should not exist anymore")
+
+  def testKill(self):
+    pid_file = self.f_dpn('child')
+    r_fd, w_fd = os.pipe()
+    new_pid = os.fork()
+    if new_pid == 0: #child
+      utils.WritePidFile(self.f_dpn('child'))
+      os.write(w_fd, 'a')
+      signal.pause()
+      os._exit(0)
+      return
+    # else we are in the parent
+    # wait until the child has written the pid file
+    os.read(r_fd, 1)
+    read_pid = utils.ReadPidFile(pid_file)
+    self.failUnlessEqual(read_pid, new_pid)
+    self.failUnless(utils.IsProcessAlive(new_pid))
+    utils.KillProcess(new_pid, waitpid=True)
+    self.failIf(utils.IsProcessAlive(new_pid))
+    utils.RemovePidFile(self.f_dpn('child'))
+    self.failUnlessRaises(errors.ProgrammerError, utils.KillProcess, 0)
+
+  def tearDown(self):
+    shutil.rmtree(self.dir)
+
+
+class TestSshKeys(testutils.GanetiTestCase):
+  """Test case for the AddAuthorizedKey function"""
+
+  KEY_A = 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a'
+  KEY_B = ('command="/usr/bin/fooserver -t --verbose",from="198.51.100.4" '
+           'ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b')
+
+  def setUp(self):
+    testutils.GanetiTestCase.setUp(self)
+    self.tmpname = self._CreateTempFile()
+    handle = open(self.tmpname, 'w')
+    try:
+      handle.write("%s\n" % TestSshKeys.KEY_A)
+      handle.write("%s\n" % TestSshKeys.KEY_B)
+    finally:
+      handle.close()
+
+  def testAddingNewKey(self):
+    utils.AddAuthorizedKey(self.tmpname,
+                           'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
+
+    self.assertFileContent(self.tmpname,
+      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
+      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
+      "ssh-dss AAAAB3NzaC1kc3MAAACB root@test\n")
+
+  def testAddingAlmostButNotCompletelyTheSameKey(self):
+    utils.AddAuthorizedKey(self.tmpname,
+        'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
+
+    self.assertFileContent(self.tmpname,
+      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
+      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
+      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test\n")
+
+  def testAddingExistingKeyWithSomeMoreSpaces(self):
+    utils.AddAuthorizedKey(self.tmpname,
+        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
+
+    self.assertFileContent(self.tmpname,
+      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
+      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
+
+  def testRemovingExistingKeyWithSomeMoreSpaces(self):
+    utils.RemoveAuthorizedKey(self.tmpname,
+        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
+
+    self.assertFileContent(self.tmpname,
+      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
+      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
+
+  def testRemovingNonExistingKey(self):
+    utils.RemoveAuthorizedKey(self.tmpname,
+        'ssh-dss  AAAAB3Nsdfj230xxjxJjsjwjsjdjU   root@test')
+
+    self.assertFileContent(self.tmpname,
+      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
+      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
+      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
+
+
+if __name__ == "__main__":
+  testutils.GanetiTestProgram()
diff --git a/test/ganeti.utils_unittest.py b/test/ganeti.utils_unittest.py
index e7b0298f8..c1cb0b6a2 100755
--- a/test/ganeti.utils_unittest.py
+++ b/test/ganeti.utils_unittest.py
@@ -46,10 +46,10 @@ from ganeti import constants
 from ganeti import compat
 from ganeti import utils
 from ganeti import errors
-from ganeti.utils import RunCmd, RemoveFile, \
-     ListVisibleFiles, FirstFree, \
-     TailFile, RunParts, PathJoin, \
-     ReadOneLineFile, SetEtcHostsEntry, RemoveEtcHostsEntry
+from ganeti.utils import RunCmd, \
+     FirstFree, \
+     RunParts, \
+     SetEtcHostsEntry, RemoveEtcHostsEntry
 
 
 class TestIsProcessAlive(unittest.TestCase):
@@ -112,7 +112,7 @@ class TestIsProcessHandlingSignal(unittest.TestCase):
         self.assertEqual(result, value.strip())
 
   def test(self):
-    sp = PathJoin(self.tmpdir, "status")
+    sp = utils.PathJoin(self.tmpdir, "status")
 
     utils.WriteFile(sp, data="\n".join([
       "Name:   bash",
@@ -130,7 +130,7 @@ class TestIsProcessHandlingSignal(unittest.TestCase):
     self.assert_(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
 
   def testNoSigCgt(self):
-    sp = PathJoin(self.tmpdir, "status")
+    sp = utils.PathJoin(self.tmpdir, "status")
 
     utils.WriteFile(sp, data="\n".join([
       "Name:   bash",
@@ -140,7 +140,7 @@ class TestIsProcessHandlingSignal(unittest.TestCase):
                       1234, 10, status_path=sp)
 
   def testNoSuchFile(self):
-    sp = PathJoin(self.tmpdir, "notexist")
+    sp = utils.PathJoin(self.tmpdir, "notexist")
 
     self.assertFalse(utils.IsProcessHandlingSignal(1234, 10, status_path=sp))
 
@@ -168,68 +168,6 @@ class TestIsProcessHandlingSignal(unittest.TestCase):
     self.assert_(utils.RunInSeparateProcess(self._TestRealProcess))
 
 
-class TestPidFileFunctions(unittest.TestCase):
-  """Tests for WritePidFile, RemovePidFile and ReadPidFile"""
-
-  def setUp(self):
-    self.dir = tempfile.mkdtemp()
-    self.f_dpn = lambda name: os.path.join(self.dir, "%s.pid" % name)
-
-  def testPidFileFunctions(self):
-    pid_file = self.f_dpn('test')
-    fd = utils.WritePidFile(self.f_dpn('test'))
-    self.failUnless(os.path.exists(pid_file),
-                    "PID file should have been created")
-    read_pid = utils.ReadPidFile(pid_file)
-    self.failUnlessEqual(read_pid, os.getpid())
-    self.failUnless(utils.IsProcessAlive(read_pid))
-    self.failUnlessRaises(errors.LockError, utils.WritePidFile,
-                          self.f_dpn('test'))
-    os.close(fd)
-    utils.RemovePidFile(self.f_dpn("test"))
-    self.failIf(os.path.exists(pid_file),
-                "PID file should not exist anymore")
-    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
-                         "ReadPidFile should return 0 for missing pid file")
-    fh = open(pid_file, "w")
-    fh.write("blah\n")
-    fh.close()
-    self.failUnlessEqual(utils.ReadPidFile(pid_file), 0,
-                         "ReadPidFile should return 0 for invalid pid file")
-    # but now, even with the file existing, we should be able to lock it
-    fd = utils.WritePidFile(self.f_dpn('test'))
-    os.close(fd)
-    utils.RemovePidFile(self.f_dpn("test"))
-    self.failIf(os.path.exists(pid_file),
-                "PID file should not exist anymore")
-
-  def testKill(self):
-    pid_file = self.f_dpn('child')
-    r_fd, w_fd = os.pipe()
-    new_pid = os.fork()
-    if new_pid == 0: #child
-      utils.WritePidFile(self.f_dpn('child'))
-      os.write(w_fd, 'a')
-      signal.pause()
-      os._exit(0)
-      return
-    # else we are in the parent
-    # wait until the child has written the pid file
-    os.read(r_fd, 1)
-    read_pid = utils.ReadPidFile(pid_file)
-    self.failUnlessEqual(read_pid, new_pid)
-    self.failUnless(utils.IsProcessAlive(new_pid))
-    utils.KillProcess(new_pid, waitpid=True)
-    self.failIf(utils.IsProcessAlive(new_pid))
-    utils.RemovePidFile(self.f_dpn('child'))
-    self.failUnlessRaises(errors.ProgrammerError, utils.KillProcess, 0)
-
-  def tearDown(self):
-    for name in os.listdir(self.dir):
-      os.unlink(os.path.join(self.dir, name))
-    os.rmdir(self.dir)
-
-
 class TestRunCmd(testutils.GanetiTestCase):
   """Testing case for the RunCmd function"""
 
@@ -627,263 +565,6 @@ class TestStartDaemon(testutils.GanetiTestCase):
       os.close(fd)
 
 
-class TestRemoveFile(unittest.TestCase):
-  """Test case for the RemoveFile function"""
-
-  def setUp(self):
-    """Create a temp dir and file for each case"""
-    self.tmpdir = tempfile.mkdtemp('', 'ganeti-unittest-')
-    fd, self.tmpfile = tempfile.mkstemp('', '', self.tmpdir)
-    os.close(fd)
-
-  def tearDown(self):
-    if os.path.exists(self.tmpfile):
-      os.unlink(self.tmpfile)
-    os.rmdir(self.tmpdir)
-
-  def testIgnoreDirs(self):
-    """Test that RemoveFile() ignores directories"""
-    self.assertEqual(None, RemoveFile(self.tmpdir))
-
-  def testIgnoreNotExisting(self):
-    """Test that RemoveFile() ignores non-existing files"""
-    RemoveFile(self.tmpfile)
-    RemoveFile(self.tmpfile)
-
-  def testRemoveFile(self):
-    """Test that RemoveFile does remove a file"""
-    RemoveFile(self.tmpfile)
-    if os.path.exists(self.tmpfile):
-      self.fail("File '%s' not removed" % self.tmpfile)
-
-  def testRemoveSymlink(self):
-    """Test that RemoveFile does remove symlinks"""
-    symlink = self.tmpdir + "/symlink"
-    os.symlink("no-such-file", symlink)
-    RemoveFile(symlink)
-    if os.path.exists(symlink):
-      self.fail("File '%s' not removed" % symlink)
-    os.symlink(self.tmpfile, symlink)
-    RemoveFile(symlink)
-    if os.path.exists(symlink):
-      self.fail("File '%s' not removed" % symlink)
-
-
-class TestRemoveDir(unittest.TestCase):
-  def setUp(self):
-    self.tmpdir = tempfile.mkdtemp()
-
-  def tearDown(self):
-    try:
-      shutil.rmtree(self.tmpdir)
-    except EnvironmentError:
-      pass
-
-  def testEmptyDir(self):
-    utils.RemoveDir(self.tmpdir)
-    self.assertFalse(os.path.isdir(self.tmpdir))
-
-  def testNonEmptyDir(self):
-    self.tmpfile = os.path.join(self.tmpdir, "test1")
-    open(self.tmpfile, "w").close()
-    self.assertRaises(EnvironmentError, utils.RemoveDir, self.tmpdir)
-
-
-class TestRename(unittest.TestCase):
-  """Test case for RenameFile"""
-
-  def setUp(self):
-    """Create a temporary directory"""
-    self.tmpdir = tempfile.mkdtemp()
-    self.tmpfile = os.path.join(self.tmpdir, "test1")
-
-    # Touch the file
-    open(self.tmpfile, "w").close()
-
-  def tearDown(self):
-    """Remove temporary directory"""
-    shutil.rmtree(self.tmpdir)
-
-  def testSimpleRename1(self):
-    """Simple rename 1"""
-    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "xyz"))
-    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "xyz")))
-
-  def testSimpleRename2(self):
-    """Simple rename 2"""
-    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "xyz"),
-                     mkdir=True)
-    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "xyz")))
-
-  def testRenameMkdir(self):
-    """Rename with mkdir"""
-    utils.RenameFile(self.tmpfile, os.path.join(self.tmpdir, "test/xyz"),
-                     mkdir=True)
-    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test")))
-    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "test/xyz")))
-
-    utils.RenameFile(os.path.join(self.tmpdir, "test/xyz"),
-                     os.path.join(self.tmpdir, "test/foo/bar/baz"),
-                     mkdir=True)
-    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test")))
-    self.assert_(os.path.isdir(os.path.join(self.tmpdir, "test/foo/bar")))
-    self.assert_(os.path.isfile(os.path.join(self.tmpdir, "test/foo/bar/baz")))
-
-
-class TestReadFile(testutils.GanetiTestCase):
-
-  def testReadAll(self):
-    data = utils.ReadFile(self._TestDataFilename("cert1.pem"))
-    self.assertEqual(len(data), 814)
-
-    h = compat.md5_hash()
-    h.update(data)
-    self.assertEqual(h.hexdigest(), "a491efb3efe56a0535f924d5f8680fd4")
-
-  def testReadSize(self):
-    data = utils.ReadFile(self._TestDataFilename("cert1.pem"),
-                          size=100)
-    self.assertEqual(len(data), 100)
-
-    h = compat.md5_hash()
-    h.update(data)
-    self.assertEqual(h.hexdigest(), "893772354e4e690b9efd073eed433ce7")
-
-  def testError(self):
-    self.assertRaises(EnvironmentError, utils.ReadFile,
-                      "/dev/null/does-not-exist")
-
-
-class TestReadOneLineFile(testutils.GanetiTestCase):
-
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-  def testDefault(self):
-    data = ReadOneLineFile(self._TestDataFilename("cert1.pem"))
-    self.assertEqual(len(data), 27)
-    self.assertEqual(data, "-----BEGIN CERTIFICATE-----")
-
-  def testNotStrict(self):
-    data = ReadOneLineFile(self._TestDataFilename("cert1.pem"), strict=False)
-    self.assertEqual(len(data), 27)
-    self.assertEqual(data, "-----BEGIN CERTIFICATE-----")
-
-  def testStrictFailure(self):
-    self.assertRaises(errors.GenericError, ReadOneLineFile,
-                      self._TestDataFilename("cert1.pem"), strict=True)
-
-  def testLongLine(self):
-    dummydata = (1024 * "Hello World! ")
-    myfile = self._CreateTempFile()
-    utils.WriteFile(myfile, data=dummydata)
-    datastrict = ReadOneLineFile(myfile, strict=True)
-    datalax = ReadOneLineFile(myfile, strict=False)
-    self.assertEqual(dummydata, datastrict)
-    self.assertEqual(dummydata, datalax)
-
-  def testNewline(self):
-    myfile = self._CreateTempFile()
-    myline = "myline"
-    for nl in ["", "\n", "\r\n"]:
-      dummydata = "%s%s" % (myline, nl)
-      utils.WriteFile(myfile, data=dummydata)
-      datalax = ReadOneLineFile(myfile, strict=False)
-      self.assertEqual(myline, datalax)
-      datastrict = ReadOneLineFile(myfile, strict=True)
-      self.assertEqual(myline, datastrict)
-
-  def testWhitespaceAndMultipleLines(self):
-    myfile = self._CreateTempFile()
-    for nl in ["", "\n", "\r\n"]:
-      for ws in [" ", "\t", "\t\t  \t", "\t "]:
-        dummydata = (1024 * ("Foo bar baz %s%s" % (ws, nl)))
-        utils.WriteFile(myfile, data=dummydata)
-        datalax = ReadOneLineFile(myfile, strict=False)
-        if nl:
-          self.assert_(set("\r\n") & set(dummydata))
-          self.assertRaises(errors.GenericError, ReadOneLineFile,
-                            myfile, strict=True)
-          explen = len("Foo bar baz ") + len(ws)
-          self.assertEqual(len(datalax), explen)
-          self.assertEqual(datalax, dummydata[:explen])
-          self.assertFalse(set("\r\n") & set(datalax))
-        else:
-          datastrict = ReadOneLineFile(myfile, strict=True)
-          self.assertEqual(dummydata, datastrict)
-          self.assertEqual(dummydata, datalax)
-
-  def testEmptylines(self):
-    myfile = self._CreateTempFile()
-    myline = "myline"
-    for nl in ["\n", "\r\n"]:
-      for ol in ["", "otherline"]:
-        dummydata = "%s%s%s%s%s%s" % (nl, nl, myline, nl, ol, nl)
-        utils.WriteFile(myfile, data=dummydata)
-        self.assert_(set("\r\n") & set(dummydata))
-        datalax = ReadOneLineFile(myfile, strict=False)
-        self.assertEqual(myline, datalax)
-        if ol:
-          self.assertRaises(errors.GenericError, ReadOneLineFile,
-                            myfile, strict=True)
-        else:
-          datastrict = ReadOneLineFile(myfile, strict=True)
-          self.assertEqual(myline, datastrict)
-
-  def testEmptyfile(self):
-    myfile = self._CreateTempFile()
-    self.assertRaises(errors.GenericError, ReadOneLineFile, myfile)
-
-
-class TestTimestampForFilename(unittest.TestCase):
-  def test(self):
-    self.assert_("." not in utils.TimestampForFilename())
-    self.assert_(":" not in utils.TimestampForFilename())
-
-
-class TestCreateBackup(testutils.GanetiTestCase):
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-
-    self.tmpdir = tempfile.mkdtemp()
-
-  def tearDown(self):
-    testutils.GanetiTestCase.tearDown(self)
-
-    shutil.rmtree(self.tmpdir)
-
-  def testEmpty(self):
-    filename = PathJoin(self.tmpdir, "config.data")
-    utils.WriteFile(filename, data="")
-    bname = utils.CreateBackup(filename)
-    self.assertFileContent(bname, "")
-    self.assertEqual(len(glob.glob("%s*" % filename)), 2)
-    utils.CreateBackup(filename)
-    self.assertEqual(len(glob.glob("%s*" % filename)), 3)
-    utils.CreateBackup(filename)
-    self.assertEqual(len(glob.glob("%s*" % filename)), 4)
-
-    fifoname = PathJoin(self.tmpdir, "fifo")
-    os.mkfifo(fifoname)
-    self.assertRaises(errors.ProgrammerError, utils.CreateBackup, fifoname)
-
-  def testContent(self):
-    bkpcount = 0
-    for data in ["", "X", "Hello World!\n" * 100, "Binary data\0\x01\x02\n"]:
-      for rep in [1, 2, 10, 127]:
-        testdata = data * rep
-
-        filename = PathJoin(self.tmpdir, "test.data_")
-        utils.WriteFile(filename, data=testdata)
-        self.assertFileContent(filename, testdata)
-
-        for _ in range(3):
-          bname = utils.CreateBackup(filename)
-          bkpcount += 1
-          self.assertFileContent(bname, testdata)
-          self.assertEqual(len(glob.glob("%s*" % filename)), 1 + bkpcount)
-
-
 class TestParseCpuMask(unittest.TestCase):
   """Test case for the ParseCpuMask function."""
 
@@ -897,70 +578,6 @@ class TestParseCpuMask(unittest.TestCase):
       self.assertRaises(errors.ParseError, utils.ParseCpuMask, data)
 
 
-class TestSshKeys(testutils.GanetiTestCase):
-  """Test case for the AddAuthorizedKey function"""
-
-  KEY_A = 'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a'
-  KEY_B = ('command="/usr/bin/fooserver -t --verbose",from="198.51.100.4" '
-           'ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b')
-
-  def setUp(self):
-    testutils.GanetiTestCase.setUp(self)
-    self.tmpname = self._CreateTempFile()
-    handle = open(self.tmpname, 'w')
-    try:
-      handle.write("%s\n" % TestSshKeys.KEY_A)
-      handle.write("%s\n" % TestSshKeys.KEY_B)
-    finally:
-      handle.close()
-
-  def testAddingNewKey(self):
-    utils.AddAuthorizedKey(self.tmpname,
-                           'ssh-dss AAAAB3NzaC1kc3MAAACB root@test')
-
-    self.assertFileContent(self.tmpname,
-      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
-      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
-      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
-      "ssh-dss AAAAB3NzaC1kc3MAAACB root@test\n")
-
-  def testAddingAlmostButNotCompletelyTheSameKey(self):
-    utils.AddAuthorizedKey(self.tmpname,
-        'ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test')
-
-    self.assertFileContent(self.tmpname,
-      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
-      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
-      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n"
-      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@test\n")
-
-  def testAddingExistingKeyWithSomeMoreSpaces(self):
-    utils.AddAuthorizedKey(self.tmpname,
-        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
-
-    self.assertFileContent(self.tmpname,
-      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
-      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
-      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
-
-  def testRemovingExistingKeyWithSomeMoreSpaces(self):
-    utils.RemoveAuthorizedKey(self.tmpname,
-        'ssh-dss  AAAAB3NzaC1w5256closdj32mZaQU   root@key-a')
-
-    self.assertFileContent(self.tmpname,
-      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
-      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
-
-  def testRemovingNonExistingKey(self):
-    utils.RemoveAuthorizedKey(self.tmpname,
-        'ssh-dss  AAAAB3Nsdfj230xxjxJjsjwjsjdjU   root@test')
-
-    self.assertFileContent(self.tmpname,
-      "ssh-dss AAAAB3NzaC1w5256closdj32mZaQU root@key-a\n"
-      'command="/usr/bin/fooserver -t --verbose",from="198.51.100.4"'
-      " ssh-dss AAAAB3NzaC1w520smc01ms0jfJs22 root@key-b\n")
-
-
 class TestEtcHosts(testutils.GanetiTestCase):
   """Test functions modifying /etc/hosts"""
 
@@ -1062,48 +679,6 @@ class TestGetMounts(unittest.TestCase):
         ("none", "/proc", "proc", "rw,nosuid,nodev,noexec,relatime"),
       ])
 
-
-class TestListVisibleFiles(unittest.TestCase):
-  """Test case for ListVisibleFiles"""
-
-  def setUp(self):
-    self.path = tempfile.mkdtemp()
-
-  def tearDown(self):
-    shutil.rmtree(self.path)
-
-  def _CreateFiles(self, files):
-    for name in files:
-      utils.WriteFile(os.path.join(self.path, name), data="test")
-
-  def _test(self, files, expected):
-    self._CreateFiles(files)
-    found = ListVisibleFiles(self.path)
-    self.assertEqual(set(found), set(expected))
-
-  def testAllVisible(self):
-    files = ["a", "b", "c"]
-    expected = files
-    self._test(files, expected)
-
-  def testNoneVisible(self):
-    files = [".a", ".b", ".c"]
-    expected = []
-    self._test(files, expected)
-
-  def testSomeVisible(self):
-    files = ["a", "b", ".c"]
-    expected = ["a", "b"]
-    self._test(files, expected)
-
-  def testNonAbsolutePath(self):
-    self.failUnlessRaises(errors.ProgrammerError, ListVisibleFiles, "abc")
-
-  def testNonNormalizedPath(self):
-    self.failUnlessRaises(errors.ProgrammerError, ListVisibleFiles,
-                          "/bin/../tmp")
-
-
 class TestNewUUID(unittest.TestCase):
   """Test case for NewUUID"""
 
@@ -1123,48 +698,6 @@ class TestFirstFree(unittest.TestCase):
     self.failUnlessRaises(AssertionError, FirstFree, [0, 3, 4, 6], base=3)
 
 
-class TestTailFile(testutils.GanetiTestCase):
-  """Test case for the TailFile function"""
-
-  def testEmpty(self):
-    fname = self._CreateTempFile()
-    self.failUnlessEqual(TailFile(fname), [])
-    self.failUnlessEqual(TailFile(fname, lines=25), [])
-
-  def testAllLines(self):
-    data = ["test %d" % i for i in range(30)]
-    for i in range(30):
-      fname = self._CreateTempFile()
-      fd = open(fname, "w")
-      fd.write("\n".join(data[:i]))
-      if i > 0:
-        fd.write("\n")
-      fd.close()
-      self.failUnlessEqual(TailFile(fname, lines=i), data[:i])
-
-  def testPartialLines(self):
-    data = ["test %d" % i for i in range(30)]
-    fname = self._CreateTempFile()
-    fd = open(fname, "w")
-    fd.write("\n".join(data))
-    fd.write("\n")
-    fd.close()
-    for i in range(1, 30):
-      self.failUnlessEqual(TailFile(fname, lines=i), data[-i:])
-
-  def testBigFile(self):
-    data = ["test %d" % i for i in range(30)]
-    fname = self._CreateTempFile()
-    fd = open(fname, "w")
-    fd.write("X" * 1048576)
-    fd.write("\n")
-    fd.write("\n".join(data))
-    fd.write("\n")
-    fd.close()
-    for i in range(1, 30):
-      self.failUnlessEqual(TailFile(fname, lines=i), data[-i:])
-
-
 class TestTimeFunctions(unittest.TestCase):
   """Test case for time functions"""
 
@@ -1266,25 +799,6 @@ class TestForceDictType(unittest.TestCase):
                       {"b": "hello"}, {"b": "no-such-type"})
 
 
-class TestIsNormAbsPath(unittest.TestCase):
-  """Testing case for IsNormAbsPath"""
-
-  def _pathTestHelper(self, path, result):
-    if result:
-      self.assert_(utils.IsNormAbsPath(path),
-          "Path %s should result absolute and normalized" % path)
-    else:
-      self.assertFalse(utils.IsNormAbsPath(path),
-          "Path %s should not result absolute and normalized" % path)
-
-  def testBase(self):
-    self._pathTestHelper('/etc', True)
-    self._pathTestHelper('/srv', True)
-    self._pathTestHelper('etc', False)
-    self._pathTestHelper('/etc/../root', False)
-    self._pathTestHelper('/etc/', False)
-
-
 class RunInSeparateProcess(unittest.TestCase):
   def test(self):
     for exp in [True, False]:
@@ -1370,23 +884,6 @@ class TestGenerateSelfSignedX509Cert(unittest.TestCase):
     self.assert_(self._checkCertificate(cert1))
 
 
-class TestPathJoin(unittest.TestCase):
-  """Testing case for PathJoin"""
-
-  def testBasicItems(self):
-    mlist = ["/a", "b", "c"]
-    self.failUnlessEqual(PathJoin(*mlist), "/".join(mlist))
-
-  def testNonAbsPrefix(self):
-    self.failUnlessRaises(ValueError, PathJoin, "a", "b")
-
-  def testBackTrack(self):
-    self.failUnlessRaises(ValueError, PathJoin, "/a", "b/../c")
-
-  def testMultiAbs(self):
-    self.failUnlessRaises(ValueError, PathJoin, "/a", "/b")
-
-
 class TestValidateServiceName(unittest.TestCase):
   def testValid(self):
     testnames = [
@@ -1531,37 +1028,6 @@ class TestSignX509Certificate(unittest.TestCase):
                       pem, self.KEY_OTHER)
 
 
-class TestMakedirs(unittest.TestCase):
-  def setUp(self):
-    self.tmpdir = tempfile.mkdtemp()
-
-  def tearDown(self):
-    shutil.rmtree(self.tmpdir)
-
-  def testNonExisting(self):
-    path = PathJoin(self.tmpdir, "foo")
-    utils.Makedirs(path)
-    self.assert_(os.path.isdir(path))
-
-  def testExisting(self):
-    path = PathJoin(self.tmpdir, "foo")
-    os.mkdir(path)
-    utils.Makedirs(path)
-    self.assert_(os.path.isdir(path))
-
-  def testRecursiveNonExisting(self):
-    path = PathJoin(self.tmpdir, "foo/bar/baz")
-    utils.Makedirs(path)
-    self.assert_(os.path.isdir(path))
-
-  def testRecursiveExisting(self):
-    path = PathJoin(self.tmpdir, "B/moo/xyz")
-    self.assertFalse(os.path.exists(path))
-    os.mkdir(PathJoin(self.tmpdir, "B"))
-    utils.Makedirs(path)
-    self.assert_(os.path.isdir(path))
-
-
 class TestReadLockedPidFile(unittest.TestCase):
   def setUp(self):
     self.tmpdir = tempfile.mkdtemp()
@@ -1570,16 +1036,16 @@ class TestReadLockedPidFile(unittest.TestCase):
     shutil.rmtree(self.tmpdir)
 
   def testNonExistent(self):
-    path = PathJoin(self.tmpdir, "nonexist")
+    path = utils.PathJoin(self.tmpdir, "nonexist")
     self.assert_(utils.ReadLockedPidFile(path) is None)
 
   def testUnlocked(self):
-    path = PathJoin(self.tmpdir, "pid")
+    path = utils.PathJoin(self.tmpdir, "pid")
     utils.WriteFile(path, data="123")
     self.assert_(utils.ReadLockedPidFile(path) is None)
 
   def testLocked(self):
-    path = PathJoin(self.tmpdir, "pid")
+    path = utils.PathJoin(self.tmpdir, "pid")
     utils.WriteFile(path, data="123")
 
     fl = utils.FileLock.Open(path)
@@ -1593,8 +1059,8 @@ class TestReadLockedPidFile(unittest.TestCase):
     self.assert_(utils.ReadLockedPidFile(path) is None)
 
   def testError(self):
-    path = PathJoin(self.tmpdir, "foobar", "pid")
-    utils.WriteFile(PathJoin(self.tmpdir, "foobar"), data="")
+    path = utils.PathJoin(self.tmpdir, "foobar", "pid")
+    utils.WriteFile(utils.PathJoin(self.tmpdir, "foobar"), data="")
     # open(2) should return ENOTDIR
     self.assertRaises(EnvironmentError, utils.ReadLockedPidFile, path)
 
@@ -1653,28 +1119,6 @@ class TestVerifyCertificateInner(unittest.TestCase):
     self.assertEqual(errcode, utils.CERT_ERROR)
 
 
-class TestEnsureDirs(unittest.TestCase):
-  """Tests for EnsureDirs"""
-
-  def setUp(self):
-    self.dir = tempfile.mkdtemp()
-    self.old_umask = os.umask(0777)
-
-  def testEnsureDirs(self):
-    utils.EnsureDirs([
-        (PathJoin(self.dir, "foo"), 0777),
-        (PathJoin(self.dir, "bar"), 0000),
-        ])
-    self.assertEquals(os.stat(PathJoin(self.dir, "foo"))[0] & 0777, 0777)
-    self.assertEquals(os.stat(PathJoin(self.dir, "bar"))[0] & 0777, 0000)
-
-  def tearDown(self):
-    os.rmdir(PathJoin(self.dir, "foo"))
-    os.rmdir(PathJoin(self.dir, "bar"))
-    os.rmdir(self.dir)
-    os.umask(self.old_umask)
-
-
 class TestFindMatch(unittest.TestCase):
   def test(self):
     data = {
@@ -1705,45 +1149,6 @@ class TestFindMatch(unittest.TestCase):
     self.assert_(utils.FindMatch(data, "Hello World") is None)
 
 
-class TestFileID(testutils.GanetiTestCase):
-  def testEquality(self):
-    name = self._CreateTempFile()
-    oldi = utils.GetFileID(path=name)
-    self.failUnless(utils.VerifyFileID(oldi, oldi))
-
-  def testUpdate(self):
-    name = self._CreateTempFile()
-    oldi = utils.GetFileID(path=name)
-    os.utime(name, None)
-    fd = os.open(name, os.O_RDWR)
-    try:
-      newi = utils.GetFileID(fd=fd)
-      self.failUnless(utils.VerifyFileID(oldi, newi))
-      self.failUnless(utils.VerifyFileID(newi, oldi))
-    finally:
-      os.close(fd)
-
-  def testWriteFile(self):
-    name = self._CreateTempFile()
-    oldi = utils.GetFileID(path=name)
-    mtime = oldi[2]
-    os.utime(name, (mtime + 10, mtime + 10))
-    self.assertRaises(errors.LockError, utils.SafeWriteFile, name,
-                      oldi, data="")
-    os.utime(name, (mtime - 10, mtime - 10))
-    utils.SafeWriteFile(name, oldi, data="")
-    oldi = utils.GetFileID(path=name)
-    mtime = oldi[2]
-    os.utime(name, (mtime + 10, mtime + 10))
-    # this doesn't raise, since we passed None
-    utils.SafeWriteFile(name, None, data="")
-
-  def testError(self):
-    t = tempfile.NamedTemporaryFile()
-    self.assertRaises(errors.ProgrammerError, utils.GetFileID,
-                      path=t.name, fd=t.fileno())
-
-
 class TimeMock:
   def __init__(self, values):
     self.values = values
@@ -1807,68 +1212,5 @@ class TestBuildShellCmd(unittest.TestCase):
     self.assertEqual(utils.BuildShellCmd("ls %s", "ab"), "ls ab")
 
 
-class TestWriteFile(unittest.TestCase):
-  def setUp(self):
-    self.tfile = tempfile.NamedTemporaryFile()
-    self.did_pre = False
-    self.did_post = False
-    self.did_write = False
-
-  def markPre(self, fd):
-    self.did_pre = True
-
-  def markPost(self, fd):
-    self.did_post = True
-
-  def markWrite(self, fd):
-    self.did_write = True
-
-  def testWrite(self):
-    data = "abc"
-    utils.WriteFile(self.tfile.name, data=data)
-    self.assertEqual(utils.ReadFile(self.tfile.name), data)
-
-  def testErrors(self):
-    self.assertRaises(errors.ProgrammerError, utils.WriteFile,
-                      self.tfile.name, data="test", fn=lambda fd: None)
-    self.assertRaises(errors.ProgrammerError, utils.WriteFile, self.tfile.name)
-    self.assertRaises(errors.ProgrammerError, utils.WriteFile,
-                      self.tfile.name, data="test", atime=0)
-
-  def testCalls(self):
-    utils.WriteFile(self.tfile.name, fn=self.markWrite,
-                    prewrite=self.markPre, postwrite=self.markPost)
-    self.assertTrue(self.did_pre)
-    self.assertTrue(self.did_post)
-    self.assertTrue(self.did_write)
-
-  def testDryRun(self):
-    orig = "abc"
-    self.tfile.write(orig)
-    self.tfile.flush()
-    utils.WriteFile(self.tfile.name, data="hello", dry_run=True)
-    self.assertEqual(utils.ReadFile(self.tfile.name), orig)
-
-  def testTimes(self):
-    f = self.tfile.name
-    for at, mt in [(0, 0), (1000, 1000), (2000, 3000),
-                   (int(time.time()), 5000)]:
-      utils.WriteFile(f, data="hello", atime=at, mtime=mt)
-      st = os.stat(f)
-      self.assertEqual(st.st_atime, at)
-      self.assertEqual(st.st_mtime, mt)
-
-
-  def testNoClose(self):
-    data = "hello"
-    self.assertEqual(utils.WriteFile(self.tfile.name, data="abc"), None)
-    fd = utils.WriteFile(self.tfile.name, data=data, close=False)
-    try:
-      os.lseek(fd, 0, 0)
-      self.assertEqual(os.read(fd, 4096), data)
-    finally:
-      os.close(fd)
-
-
 if __name__ == '__main__':
   testutils.GanetiTestProgram()
-- 
GitLab