Add a test opcode that sleeps for a given duration
[ganeti-local] / lib / utils.py
index d5d604b..6cbd268 100644 (file)
@@ -20,6 +20,7 @@
 
 
 """Ganeti small utilities
+
 """
 
 
@@ -35,14 +36,21 @@ import shutil
 import errno
 import pwd
 import itertools
+import select
+import fcntl
+
+from cStringIO import StringIO
 
 from ganeti import logger
 from ganeti import errors
+from ganeti import constants
 
 
 _locksheld = []
 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
 
+debug = False
+
 class RunResult(object):
   """Simple class for holding the result of running external programs.
 
@@ -77,6 +85,10 @@ class RunResult(object):
     else:
       self.fail_reason = "unable to determine termination reason"
 
+    if debug and self.failed:
+      logger.Debug("Command '%s' failed (%s); output: %s" %
+                   (self.cmd, self.fail_reason, self.output))
+
   def _GetOutput(self):
     """Returns the combined stdout and stderr for easier usage.
 
@@ -88,7 +100,7 @@ class RunResult(object):
 
 def _GetLockFile(subsystem):
   """Compute the file name for a given lock name."""
-  return "/var/lock/ganeti_lock_%s" % subsystem
+  return "%s/ganeti_lock_%s" % (constants.LOCK_DIR, subsystem)
 
 
 def Lock(name, max_retries=None, debug=False):
@@ -214,6 +226,7 @@ def RunCmd(cmd):
     shell = True
   env = os.environ.copy()
   env["LC_ALL"] = "C"
+  poller = select.poll()
   child = subprocess.Popen(cmd, shell=shell,
                            stderr=subprocess.PIPE,
                            stdout=subprocess.PIPE,
@@ -221,8 +234,35 @@ def RunCmd(cmd):
                            close_fds=True, env=env)
 
   child.stdin.close()
-  out = child.stdout.read()
-  err = child.stderr.read()
+  poller.register(child.stdout, select.POLLIN)
+  poller.register(child.stderr, select.POLLIN)
+  out = StringIO()
+  err = StringIO()
+  fdmap = {
+    child.stdout.fileno(): (out, child.stdout),
+    child.stderr.fileno(): (err, child.stderr),
+    }
+  for fd in fdmap:
+    status = fcntl.fcntl(fd, fcntl.F_GETFL)
+    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
+
+  while fdmap:
+    for fd, event in poller.poll():
+      if event & select.POLLIN or event & select.POLLPRI:
+        data = fdmap[fd][1].read()
+        # no data from read signifies EOF (the same as POLLHUP)
+        if not data:
+          poller.unregister(fd)
+          del fdmap[fd]
+          continue
+        fdmap[fd][0].write(data)
+      if (event & select.POLLNVAL or event & select.POLLHUP or
+          event & select.POLLERR):
+        poller.unregister(fd)
+        del fdmap[fd]
+
+  out = out.getvalue()
+  err = err.getvalue()
 
   status = child.wait()
   if status >= 0:
@@ -417,6 +457,12 @@ class HostInfo:
     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
     self.ip = self.ipaddrs[0]
 
+  def ShortName(self):
+    """Returns the hostname without domain.
+
+    """
+    return self.name.split('.')[0]
+
   @staticmethod
   def SysName():
     """Return the current system's name.
@@ -724,21 +770,98 @@ def RemoveAuthorizedKey(file_name, key):
   key_fields = key.split()
 
   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
-  out = os.fdopen(fd, 'w')
   try:
-    f = open(file_name, 'r')
+    out = os.fdopen(fd, 'w')
     try:
-      for line in f:
-        # Ignore whitespace changes while comparing lines
-        if line.split() != key_fields:
+      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.
+
+  """
+  # Ensure aliases are unique
+  aliases = UniqueSequence([hostname] + aliases)[1:]
+
+  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
+  try:
+    out = os.fdopen(fd, 'w')
+    try:
+      f = open(file_name, 'r')
+      try:
+        written = False
+        for line in f:
+          fields = line.split()
+          if fields and not fields[0].startswith('#') and ip == fields[0]:
+            continue
           out.write(line)
 
-      out.flush()
-      os.rename(tmpname, file_name)
+        out.write("%s\t%s" % (ip, hostname))
+        if aliases:
+          out.write(" %s" % ' '.join(aliases))
+        out.write('\n')
+
+        out.flush()
+        os.fsync(out)
+        os.rename(tmpname, file_name)
+      finally:
+        f.close()
     finally:
-      f.close()
-  finally:
-    out.close()
+      out.close()
+  except:
+    RemoveFile(tmpname)
+    raise
+
+
+def RemoveEtcHostsEntry(file_name, hostname):
+  """Removes a hostname from /etc/hosts.
+
+  IP addresses without names are removed from the file.
+  """
+  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:
+          fields = line.split()
+          if len(fields) > 1 and not fields[0].startswith('#'):
+            names = fields[1:]
+            if hostname in names:
+              while hostname in names:
+                names.remove(hostname)
+              if names:
+                out.write("%s %s\n" % (fields[0], ' '.join(names)))
+              continue
+
+          out.write(line)
+
+        out.flush()
+        os.fsync(out)
+        os.rename(tmpname, file_name)
+      finally:
+        f.close()
+    finally:
+      out.close()
+  except:
+    RemoveFile(tmpname)
+    raise
 
 
 def CreateBackup(file_name):
@@ -752,11 +875,11 @@ def CreateBackup(file_name):
                                 file_name)
 
   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
-  dir = os.path.dirname(file_name)
+  dir_name = os.path.dirname(file_name)
 
   fsrc = open(file_name, 'rb')
   try:
-    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir)
+    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
     fdst = os.fdopen(fd, 'wb')
     try:
       shutil.copyfileobj(fsrc, fdst)
@@ -823,7 +946,9 @@ def ListVisibleFiles(path):
   """Returns a list of all visible files in a directory.
 
   """
-  return [i for i in os.listdir(path) if not i.startswith(".")]
+  files = [i for i in os.listdir(path) if not i.startswith(".")]
+  files.sort()
+  return files
 
 
 def GetHomeDir(user, default=None):
@@ -847,7 +972,7 @@ def GetHomeDir(user, default=None):
   return result.pw_dir
 
 
-def GetUUID():
+def NewUUID():
   """Returns a random UUID.
 
   """
@@ -921,3 +1046,32 @@ def any(seq, pred=bool):
   for elem in itertools.ifilter(pred, seq):
     return True
   return False
+
+
+def UniqueSequence(seq):
+  """Returns a list with unique elements.
+
+  Element order is preserved.
+  """
+  seen = set()
+  return [i for i in seq if i not in seen and not seen.add(i)]
+
+
+def IsValidMac(mac):
+  """Predicate to check if a MAC address is valid.
+
+  Checks wether the supplied MAC address is formally correct, only
+  accepts colon separated format.
+  """
+  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
+  return mac_check.match(mac) is not None
+
+
+def TestDelay(duration):
+  """Sleep for a fixed amount of time.
+
+  """
+  if duration < 0:
+    return False
+  time.sleep(duration)
+  return True