Support arguments in utils.RunInSeparateProcess
[ganeti-local] / lib / backend.py
index 886ba27..367e89c 100644 (file)
 # 02110-1301, USA.
 
 
 # 02110-1301, USA.
 
 
-"""Functions used by the node daemon"""
+"""Functions used by the node daemon
+
+@var _ALLOWED_UPLOAD_FILES: denotes which files are accepted in
+     the L{UploadFile} function
+@var _ALLOWED_CLEAN_DIRS: denotes which directories are accepted
+     in the L{_CleanDirectory} function
+
+"""
+
+# pylint: disable-msg=E1103
+
+# E1103: %s %r has no %r member (but some types could not be
+# inferred), because the _TryOSFromDisk returns either (True, os_obj)
+# or (False, "string") which confuses pylint
 
 
 import os
 
 
 import os
@@ -29,7 +42,6 @@ import time
 import stat
 import errno
 import re
 import stat
 import errno
 import re
-import subprocess
 import random
 import logging
 import tempfile
 import random
 import logging
 import tempfile
@@ -46,6 +58,14 @@ from ganeti import objects
 from ganeti import ssconf
 
 
 from ganeti import ssconf
 
 
+_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
+_ALLOWED_CLEAN_DIRS = frozenset([
+  constants.DATA_DIR,
+  constants.JOB_QUEUE_ARCHIVE_DIR,
+  constants.QUEUE_DIR,
+  ])
+
+
 class RPCFail(Exception):
   """Class denoting RPC failure.
 
 class RPCFail(Exception):
   """Class denoting RPC failure.
 
@@ -53,6 +73,7 @@ class RPCFail(Exception):
 
   """
 
 
   """
 
+
 def _Fail(msg, *args, **kwargs):
   """Log an error and the raise an RPCFail exception.
 
 def _Fail(msg, *args, **kwargs):
   """Log an error and the raise an RPCFail exception.
 
@@ -129,6 +150,10 @@ def _CleanDirectory(path, exclude=None):
       to the empty list
 
   """
       to the empty list
 
   """
+  if path not in _ALLOWED_CLEAN_DIRS:
+    _Fail("Path passed to _CleanDirectory not in allowed clean targets: '%s'",
+          path)
+
   if not os.path.isdir(path):
     return
   if exclude is None:
   if not os.path.isdir(path):
     return
   if exclude is None:
@@ -138,13 +163,39 @@ def _CleanDirectory(path, exclude=None):
     exclude = [os.path.normpath(i) for i in exclude]
 
   for rel_name in utils.ListVisibleFiles(path):
     exclude = [os.path.normpath(i) for i in exclude]
 
   for rel_name in utils.ListVisibleFiles(path):
-    full_name = os.path.normpath(os.path.join(path, rel_name))
+    full_name = utils.PathJoin(path, rel_name)
     if full_name in exclude:
       continue
     if os.path.isfile(full_name) and not os.path.islink(full_name):
       utils.RemoveFile(full_name)
 
 
     if full_name in exclude:
       continue
     if os.path.isfile(full_name) and not os.path.islink(full_name):
       utils.RemoveFile(full_name)
 
 
+def _BuildUploadFileList():
+  """Build the list of allowed upload files.
+
+  This is abstracted so that it's built only once at module import time.
+
+  """
+  allowed_files = set([
+    constants.CLUSTER_CONF_FILE,
+    constants.ETC_HOSTS,
+    constants.SSH_KNOWN_HOSTS_FILE,
+    constants.VNC_PASSWORD_FILE,
+    constants.RAPI_CERT_FILE,
+    constants.RAPI_USERS_FILE,
+    constants.HMAC_CLUSTER_KEY,
+    ])
+
+  for hv_name in constants.HYPER_TYPES:
+    hv_class = hypervisor.GetHypervisorClass(hv_name)
+    allowed_files.update(hv_class.GetAncillaryFiles())
+
+  return frozenset(allowed_files)
+
+
+_ALLOWED_UPLOAD_FILES = _BuildUploadFileList()
+
+
 def JobQueuePurge():
   """Removes job queue files and archived jobs.
 
 def JobQueuePurge():
   """Removes job queue files and archived jobs.
 
@@ -154,7 +205,6 @@ def JobQueuePurge():
   """
   _CleanDirectory(constants.QUEUE_DIR, exclude=[constants.JOB_QUEUE_LOCK_FILE])
   _CleanDirectory(constants.JOB_QUEUE_ARCHIVE_DIR)
   """
   _CleanDirectory(constants.QUEUE_DIR, exclude=[constants.JOB_QUEUE_LOCK_FILE])
   _CleanDirectory(constants.JOB_QUEUE_ARCHIVE_DIR)
-  return True, None
 
 
 def GetMasterInfo():
 
 
 def GetMasterInfo():
@@ -164,7 +214,7 @@ def GetMasterInfo():
   for consumption here or from the node daemon.
 
   @rtype: tuple
   for consumption here or from the node daemon.
 
   @rtype: tuple
-  @return: True, (master_netdev, master_ip, master_name) in case of success
+  @return: master_netdev, master_ip, master_name
   @raise RPCFail: in case of errors
 
   """
   @raise RPCFail: in case of errors
 
   """
@@ -174,11 +224,11 @@ def GetMasterInfo():
     master_ip = cfg.GetMasterIP()
     master_node = cfg.GetMasterNode()
   except errors.ConfigurationError, err:
     master_ip = cfg.GetMasterIP()
     master_node = cfg.GetMasterNode()
   except errors.ConfigurationError, err:
-    _Fail("Cluster configuration incomplete", exc=True)
-  return True, (master_netdev, master_ip, master_node)
+    _Fail("Cluster configuration incomplete: %s", err, exc=True)
+  return (master_netdev, master_ip, master_node)
 
 
 
 
-def StartMaster(start_daemons):
+def StartMaster(start_daemons, no_voting):
   """Activate local node as master node.
 
   The function will always try activate the IP address of the master
   """Activate local node as master node.
 
   The function will always try activate the IP address of the master
@@ -186,15 +236,18 @@ def StartMaster(start_daemons):
   based on the start_daemons parameter.
 
   @type start_daemons: boolean
   based on the start_daemons parameter.
 
   @type start_daemons: boolean
-  @param start_daemons: whther to also start the master
+  @param start_daemons: whether to also start the master
       daemons (ganeti-masterd and ganeti-rapi)
       daemons (ganeti-masterd and ganeti-rapi)
+  @type no_voting: boolean
+  @param no_voting: whether to start ganeti-masterd without a node vote
+      (if start_daemons is True), but still non-interactively
   @rtype: None
 
   """
   # GetMasterInfo will raise an exception if not able to return data
   @rtype: None
 
   """
   # GetMasterInfo will raise an exception if not able to return data
-  master_netdev, master_ip, _ = GetMasterInfo()[1]
+  master_netdev, master_ip, _ = GetMasterInfo()
 
 
-  payload = []
+  err_msgs = []
   if utils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
     if utils.OwnIpAddress(master_ip):
       # we already have the ip:
   if utils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
     if utils.OwnIpAddress(master_ip):
       # we already have the ip:
@@ -202,7 +255,7 @@ def StartMaster(start_daemons):
     else:
       msg = "Someone else has the master ip, not activating"
       logging.error(msg)
     else:
       msg = "Someone else has the master ip, not activating"
       logging.error(msg)
-      payload.append(msg)
+      err_msgs.append(msg)
   else:
     result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip,
                            "dev", master_netdev, "label",
   else:
     result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip,
                            "dev", master_netdev, "label",
@@ -210,7 +263,7 @@ def StartMaster(start_daemons):
     if result.failed:
       msg = "Can't activate master IP: %s" % result.output
       logging.error(msg)
     if result.failed:
       msg = "Can't activate master IP: %s" % result.output
       logging.error(msg)
-      payload.append(msg)
+      err_msgs.append(msg)
 
     result = utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev,
                            "-s", master_ip, master_ip])
 
     result = utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev,
                            "-s", master_ip, master_ip])
@@ -218,17 +271,23 @@ def StartMaster(start_daemons):
 
   # and now start the master and rapi daemons
   if start_daemons:
 
   # and now start the master and rapi daemons
   if start_daemons:
-    for daemon in 'ganeti-masterd', 'ganeti-rapi':
-      result = utils.RunCmd([daemon])
-      if result.failed:
-        msg = "Can't start daemon %s: %s" % (daemon, result.output)
-        logging.error(msg)
-        payload.append(msg)
+    if no_voting:
+      masterd_args = "--no-voting --yes-do-it"
+    else:
+      masterd_args = ""
 
 
-  if payload:
-    _Fail("; ".join(payload))
+    env = {
+      "EXTRA_MASTERD_ARGS": masterd_args,
+      }
 
 
-  return True, None
+    result = utils.RunCmd([constants.DAEMON_UTIL, "start-master"], env=env)
+    if result.failed:
+      msg = "Can't start Ganeti master: %s" % result.output
+      logging.error(msg)
+      err_msgs.append(msg)
+
+  if err_msgs:
+    _Fail("; ".join(err_msgs))
 
 
 def StopMaster(stop_daemons):
 
 
 def StopMaster(stop_daemons):
@@ -248,7 +307,7 @@ def StopMaster(stop_daemons):
   # need to decide in which case we fail the RPC for this
 
   # GetMasterInfo will raise an exception if not able to return data
   # need to decide in which case we fail the RPC for this
 
   # GetMasterInfo will raise an exception if not able to return data
-  master_netdev, master_ip, _ = GetMasterInfo()[1]
+  master_netdev, master_ip, _ = GetMasterInfo()
 
   result = utils.RunCmd(["ip", "address", "del", "%s/32" % master_ip,
                          "dev", master_netdev])
 
   result = utils.RunCmd(["ip", "address", "del", "%s/32" % master_ip,
                          "dev", master_netdev])
@@ -257,11 +316,11 @@ def StopMaster(stop_daemons):
     # but otherwise ignore the failure
 
   if stop_daemons:
     # but otherwise ignore the failure
 
   if stop_daemons:
-    # stop/kill the rapi and the master daemon
-    for daemon in constants.RAPI_PID, constants.MASTERD_PID:
-      utils.KillProcess(utils.ReadPidFile(utils.DaemonPidFileName(daemon)))
-
-  return True, None
+    result = utils.RunCmd([constants.DAEMON_UTIL, "stop-master"])
+    if result.failed:
+      logging.error("Could not stop Ganeti master, command %s had exitcode %s"
+                    " and error %s",
+                    result.cmd, result.exit_code, result.output)
 
 
 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
 
 
 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
@@ -306,12 +365,13 @@ def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
 
   utils.AddAuthorizedKey(auth_keys, sshpub)
 
 
   utils.AddAuthorizedKey(auth_keys, sshpub)
 
-  utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
-
-  return (True, "Node added successfully")
+  result = utils.RunCmd([constants.DAEMON_UTIL, "reload-ssh-keys"])
+  if result.failed:
+    _Fail("Unable to reload SSH keys (command %r, exit code %s, output %r)",
+          result.cmd, result.exit_code, result.output)
 
 
 
 
-def LeaveCluster():
+def LeaveCluster(modify_ssh_setup):
   """Cleans up and remove the current node.
 
   This function cleans up and prepares the current node to be removed
   """Cleans up and remove the current node.
 
   This function cleans up and prepares the current node to be removed
@@ -321,30 +381,41 @@ def LeaveCluster():
   L{errors.QuitGanetiException} which is used as a special case to
   shutdown the node daemon.
 
   L{errors.QuitGanetiException} which is used as a special case to
   shutdown the node daemon.
 
+  @param modify_ssh_setup: boolean
+
   """
   _CleanDirectory(constants.DATA_DIR)
   JobQueuePurge()
 
   """
   _CleanDirectory(constants.DATA_DIR)
   JobQueuePurge()
 
-  try:
-    priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
-
-    f = open(pub_key, 'r')
+  if modify_ssh_setup:
     try:
     try:
-      utils.RemoveAuthorizedKey(auth_keys, f.read(8192))
-    finally:
-      f.close()
+      priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
+
+      utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
 
 
-    utils.RemoveFile(priv_key)
-    utils.RemoveFile(pub_key)
-  except errors.OpExecError:
-    logging.exception("Error while processing ssh files")
+      utils.RemoveFile(priv_key)
+      utils.RemoveFile(pub_key)
+    except errors.OpExecError:
+      logging.exception("Error while processing ssh files")
+
+  try:
+    utils.RemoveFile(constants.HMAC_CLUSTER_KEY)
+    utils.RemoveFile(constants.RAPI_CERT_FILE)
+    utils.RemoveFile(constants.SSL_CERT_FILE)
+  except: # pylint: disable-msg=W0702
+    logging.exception("Error while removing cluster secrets")
+
+  result = utils.RunCmd([constants.DAEMON_UTIL, "stop", constants.CONFD])
+  if result.failed:
+    logging.error("Command %s failed with exitcode %s and error %s",
+                  result.cmd, result.exit_code, result.output)
 
   # Raise a custom exception (handled in ganeti-noded)
   raise errors.QuitGanetiException(True, 'Shutdown scheduled')
 
 
 def GetNodeInfo(vgname, hypervisor_type):
 
   # Raise a custom exception (handled in ganeti-noded)
   raise errors.QuitGanetiException(True, 'Shutdown scheduled')
 
 
 def GetNodeInfo(vgname, hypervisor_type):
-  """Gives back a hash with different informations about the node.
+  """Gives back a hash with different information about the node.
 
   @type vgname: C{string}
   @param vgname: the name of the volume group to ask for disk space information
 
   @type vgname: C{string}
   @param vgname: the name of the volume group to ask for disk space information
@@ -370,13 +441,9 @@ def GetNodeInfo(vgname, hypervisor_type):
   if hyp_info is not None:
     outputarray.update(hyp_info)
 
   if hyp_info is not None:
     outputarray.update(hyp_info)
 
-  f = open("/proc/sys/kernel/random/boot_id", 'r')
-  try:
-    outputarray["bootid"] = f.read(128).rstrip("\n")
-  finally:
-    f.close()
+  outputarray["bootid"] = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
 
 
-  return True, outputarray
+  return outputarray
 
 
 def VerifyNode(what, cluster_name):
 
 
 def VerifyNode(what, cluster_name):
@@ -440,7 +507,7 @@ def VerifyNode(what, cluster_name):
       tmp[my_name] = ("Can't find my own primary/secondary IP"
                       " in the node list")
     else:
       tmp[my_name] = ("Can't find my own primary/secondary IP"
                       " in the node list")
     else:
-      port = utils.GetNodeDaemonPort()
+      port = utils.GetDaemonPort(constants.NODED)
       for name, pip, sip in what[constants.NV_NODENETTEST]:
         fail = []
         if not utils.TcpPing(pip, port, source=my_pip):
       for name, pip, sip in what[constants.NV_NODENETTEST]:
         fail = []
         if not utils.TcpPing(pip, port, source=my_pip):
@@ -462,6 +529,11 @@ def VerifyNode(what, cluster_name):
   if constants.NV_VGLIST in what:
     result[constants.NV_VGLIST] = utils.ListVolumeGroups()
 
   if constants.NV_VGLIST in what:
     result[constants.NV_VGLIST] = utils.ListVolumeGroups()
 
+  if constants.NV_PVLIST in what:
+    result[constants.NV_PVLIST] = \
+      bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
+                                   filter_allocatable=False)
+
   if constants.NV_VERSION in what:
     result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
                                     constants.RELEASE_VERSION)
   if constants.NV_VERSION in what:
     result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
                                     constants.RELEASE_VERSION)
@@ -478,7 +550,22 @@ def VerifyNode(what, cluster_name):
       used_minors = str(err)
     result[constants.NV_DRBDLIST] = used_minors
 
       used_minors = str(err)
     result[constants.NV_DRBDLIST] = used_minors
 
-  return True, result
+  if constants.NV_NODESETUP in what:
+    result[constants.NV_NODESETUP] = tmpr = []
+    if not os.path.isdir("/sys/block") or not os.path.isdir("/sys/class/net"):
+      tmpr.append("The sysfs filesytem doesn't seem to be mounted"
+                  " under /sys, missing required directories /sys/block"
+                  " and /sys/class/net")
+    if (not os.path.isdir("/proc/sys") or
+        not os.path.isfile("/proc/sysrq-trigger")):
+      tmpr.append("The procfs filesystem doesn't seem to be mounted"
+                  " under /proc, missing required directory /proc/sys and"
+                  " the file /proc/sysrq-trigger")
+
+  if constants.NV_TIME in what:
+    result[constants.NV_TIME] = utils.SplitTime(time.time())
+
+  return result
 
 
 def GetVolumeList(vg_name):
 
 
 def GetVolumeList(vg_name):
@@ -515,6 +602,11 @@ def GetVolumeList(vg_name):
     name, size, attr = match.groups()
     inactive = attr[4] == '-'
     online = attr[5] == 'o'
     name, size, attr = match.groups()
     inactive = attr[4] == '-'
     online = attr[5] == 'o'
+    virtual = attr[0] == 'v'
+    if virtual:
+      # we don't want to report such volumes as existing, since they
+      # don't really hold data
+      continue
     lvs[name] = (size, inactive, online)
 
   return lvs
     lvs[name] = (size, inactive, online)
 
   return lvs
@@ -528,7 +620,7 @@ def ListVolumeGroups():
       size of the volume
 
   """
       size of the volume
 
   """
-  return True, utils.ListVolumeGroups()
+  return utils.ListVolumeGroups()
 
 
 def NodeVolumes():
 
 
 def NodeVolumes():
@@ -571,9 +663,8 @@ def NodeVolumes():
       'vg': line[3].strip(),
     }
 
       'vg': line[3].strip(),
     }
 
-  return True, [map_line(line.split('|'))
-                for line in result.stdout.splitlines()
-                if line.count('|') >= 3]
+  return [map_line(line.split('|')) for line in result.stdout.splitlines()
+          if line.count('|') >= 3]
 
 
 def BridgesExist(bridges_list):
 
 
 def BridgesExist(bridges_list):
@@ -589,9 +680,7 @@ def BridgesExist(bridges_list):
       missing.append(bridge)
 
   if missing:
       missing.append(bridge)
 
   if missing:
-    _Fail("Missing bridges %s", ", ".join(missing))
-
-  return True, None
+    _Fail("Missing bridges %s", utils.CommaJoin(missing))
 
 
 def GetInstanceList(hypervisor_list):
 
 
 def GetInstanceList(hypervisor_list):
@@ -619,7 +708,7 @@ def GetInstanceList(hypervisor_list):
 
 
 def GetInstanceInfo(instance, hname):
 
 
 def GetInstanceInfo(instance, hname):
-  """Gives back the informations about an instance as a dictionary.
+  """Gives back the information about an instance as a dictionary.
 
   @type instance: string
   @param instance: the instance name
 
   @type instance: string
   @param instance: the instance name
@@ -641,7 +730,7 @@ def GetInstanceInfo(instance, hname):
     output['state'] = iinfo[4]
     output['time'] = iinfo[5]
 
     output['state'] = iinfo[4]
     output['time'] = iinfo[5]
 
-  return True, output
+  return output
 
 
 def GetInstanceMigratable(instance):
 
 
 def GetInstanceMigratable(instance):
@@ -666,8 +755,6 @@ def GetInstanceMigratable(instance):
     if not os.path.islink(link_name):
       _Fail("Instance %s was not restarted since ganeti 1.2.5", iname)
 
     if not os.path.islink(link_name):
       _Fail("Instance %s was not restarted since ganeti 1.2.5", iname)
 
-  return True, None
-
 
 def GetAllInstancesInfo(hypervisor_list):
   """Gather data about all instances.
 
 def GetAllInstancesInfo(hypervisor_list):
   """Gather data about all instances.
@@ -692,7 +779,7 @@ def GetAllInstancesInfo(hypervisor_list):
   for hname in hypervisor_list:
     iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo()
     if iinfo:
   for hname in hypervisor_list:
     iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo()
     if iinfo:
-      for name, inst_id, memory, vcpus, state, times in iinfo:
+      for name, _, memory, vcpus, state, times in iinfo:
         value = {
           'memory': memory,
           'vcpus': vcpus,
         value = {
           'memory': memory,
           'vcpus': vcpus,
@@ -709,29 +796,46 @@ def GetAllInstancesInfo(hypervisor_list):
                     " with different parameters", name)
         output[name] = value
 
                     " with different parameters", name)
         output[name] = value
 
-  return True, output
+  return output
+
+
+def _InstanceLogName(kind, os_name, instance):
+  """Compute the OS log filename for a given instance and operation.
+
+  The instance name and os name are passed in as strings since not all
+  operations have these as part of an instance object.
+
+  @type kind: string
+  @param kind: the operation type (e.g. add, import, etc.)
+  @type os_name: string
+  @param os_name: the os name
+  @type instance: string
+  @param instance: the name of the instance being imported/added/etc.
+
+  """
+  base = "%s-%s-%s-%d.log" % (kind, os_name, instance, int(time.time()))
+  return utils.PathJoin(constants.LOG_OS_DIR, base)
 
 
 
 
-def InstanceOsAdd(instance, reinstall):
+def InstanceOsAdd(instance, reinstall, debug):
   """Add an OS to an instance.
 
   @type instance: L{objects.Instance}
   @param instance: Instance whose OS is to be installed
   @type reinstall: boolean
   @param reinstall: whether this is an instance reinstall
   """Add an OS to an instance.
 
   @type instance: L{objects.Instance}
   @param instance: Instance whose OS is to be installed
   @type reinstall: boolean
   @param reinstall: whether this is an instance reinstall
-  @rtype: boolean
-  @return: the success of the operation
+  @type debug: integer
+  @param debug: debug level, passed to the OS scripts
+  @rtype: None
 
   """
   inst_os = OSFromDisk(instance.os)
 
 
   """
   inst_os = OSFromDisk(instance.os)
 
-
-  create_env = OSEnvironment(instance)
+  create_env = OSEnvironment(instance, inst_os, debug)
   if reinstall:
     create_env['INSTANCE_REINSTALL'] = "1"
 
   if reinstall:
     create_env['INSTANCE_REINSTALL'] = "1"
 
-  logfile = "%s/add-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
-                                     instance.name, int(time.time()))
+  logfile = _InstanceLogName("add", instance.os, instance.name)
 
   result = utils.RunCmd([inst_os.create_script], env=create_env,
                         cwd=inst_os.path, output=logfile,)
 
   result = utils.RunCmd([inst_os.create_script], env=create_env,
                         cwd=inst_os.path, output=logfile,)
@@ -744,28 +848,27 @@ def InstanceOsAdd(instance, reinstall):
     _Fail("OS create script failed (%s), last lines in the"
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
     _Fail("OS create script failed (%s), last lines in the"
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
-  return (True, "Successfully installed")
-
 
 
-def RunRenameInstance(instance, old_name):
+def RunRenameInstance(instance, old_name, debug):
   """Run the OS rename script for an instance.
 
   @type instance: L{objects.Instance}
   @param instance: Instance whose OS is to be installed
   @type old_name: string
   @param old_name: previous instance name
   """Run the OS rename script for an instance.
 
   @type instance: L{objects.Instance}
   @param instance: Instance whose OS is to be installed
   @type old_name: string
   @param old_name: previous instance name
+  @type debug: integer
+  @param debug: debug level, passed to the OS scripts
   @rtype: boolean
   @return: the success of the operation
 
   """
   inst_os = OSFromDisk(instance.os)
 
   @rtype: boolean
   @return: the success of the operation
 
   """
   inst_os = OSFromDisk(instance.os)
 
-  rename_env = OSEnvironment(instance)
+  rename_env = OSEnvironment(instance, inst_os, debug)
   rename_env['OLD_INSTANCE_NAME'] = old_name
 
   rename_env['OLD_INSTANCE_NAME'] = old_name
 
-  logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
-                                           old_name,
-                                           instance.name, int(time.time()))
+  logfile = _InstanceLogName("rename", instance.os,
+                             "%s-%s" % (old_name, instance.name))
 
   result = utils.RunCmd([inst_os.rename_script], env=rename_env,
                         cwd=inst_os.path, output=logfile)
 
   result = utils.RunCmd([inst_os.rename_script], env=rename_env,
                         cwd=inst_os.path, output=logfile)
@@ -778,11 +881,9 @@ def RunRenameInstance(instance, old_name):
     _Fail("OS rename script failed (%s), last lines in the"
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
     _Fail("OS rename script failed (%s), last lines in the"
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
-  return (True, "Rename successful")
-
 
 def _GetVGInfo(vg_name):
 
 def _GetVGInfo(vg_name):
-  """Get informations about the volume group.
+  """Get information about the volume group.
 
   @type vg_name: str
   @param vg_name: the volume group which we query
 
   @type vg_name: str
   @param vg_name: the volume group which we query
@@ -813,8 +914,8 @@ def _GetVGInfo(vg_name):
         "vg_free": int(round(float(valarr[1]), 0)),
         "pv_count": int(valarr[2]),
         }
         "vg_free": int(round(float(valarr[1]), 0)),
         "pv_count": int(valarr[2]),
         }
-    except ValueError, err:
-      logging.exception("Fail to parse vgs output")
+    except (TypeError, ValueError), err:
+      logging.exception("Fail to parse vgs output: %s", err)
   else:
     logging.error("vgs output has the wrong number of fields (expected"
                   " three): %s", str(valarr))
   else:
     logging.error("vgs output has the wrong number of fields (expected"
                   " three): %s", str(valarr))
@@ -822,8 +923,8 @@ def _GetVGInfo(vg_name):
 
 
 def _GetBlockDevSymlinkPath(instance_name, idx):
 
 
 def _GetBlockDevSymlinkPath(instance_name, idx):
-  return os.path.join(constants.DISK_LINKS_DIR,
-                      "%s:%d" % (instance_name, idx))
+  return utils.PathJoin(constants.DISK_LINKS_DIR,
+                        "%s:%d" % (instance_name, idx))
 
 
 def _SymlinkBlockDev(instance_name, device_path, idx):
 
 
 def _SymlinkBlockDev(instance_name, device_path, idx):
@@ -858,7 +959,7 @@ def _RemoveBlockDevLinks(instance_name, disks):
   """Remove the block device symlinks belonging to the given instance.
 
   """
   """Remove the block device symlinks belonging to the given instance.
 
   """
-  for idx, disk in enumerate(disks):
+  for idx, _ in enumerate(disks):
     link_name = _GetBlockDevSymlinkPath(instance_name, idx)
     if os.path.islink(link_name):
       try:
     link_name = _GetBlockDevSymlinkPath(instance_name, idx)
     if os.path.islink(link_name):
       try:
@@ -902,14 +1003,14 @@ def StartInstance(instance):
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
-  @rtype: boolean
-  @return: whether the startup was successful or not
+  @rtype: None
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
 
   if instance.name in running_instances:
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
 
   if instance.name in running_instances:
-    return (True, "Already running")
+    logging.info("Instance %s already running, not starting", instance.name)
+    return
 
   try:
     block_devices = _GatherAndLinkBlockDevs(instance)
 
   try:
     block_devices = _GatherAndLinkBlockDevs(instance)
@@ -921,59 +1022,72 @@ def StartInstance(instance):
     _RemoveBlockDevLinks(instance.name, instance.disks)
     _Fail("Hypervisor error: %s", err, exc=True)
 
     _RemoveBlockDevLinks(instance.name, instance.disks)
     _Fail("Hypervisor error: %s", err, exc=True)
 
-  return (True, "Instance started successfully")
 
 
-
-def InstanceShutdown(instance):
+def InstanceShutdown(instance, timeout):
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
-  @rtype: boolean
-  @return: whether the startup was successful or not
+  @type timeout: integer
+  @param timeout: maximum timeout for soft shutdown
+  @rtype: None
 
   """
   hv_name = instance.hypervisor
 
   """
   hv_name = instance.hypervisor
-  running_instances = GetInstanceList([hv_name])
+  hyper = hypervisor.GetHypervisor(hv_name)
+  iname = instance.name
 
 
-  if instance.name not in running_instances:
-    return (True, "Instance already stopped")
+  if instance.name not in hyper.ListInstances():
+    logging.info("Instance %s not running, doing nothing", iname)
+    return
 
 
-  hyper = hypervisor.GetHypervisor(hv_name)
-  try:
-    hyper.StopInstance(instance)
-  except errors.HypervisorError, err:
-    _Fail("Failed to stop instance %s: %s", instance.name, err)
+  class _TryShutdown:
+    def __init__(self):
+      self.tried_once = False
 
 
-  # test every 10secs for 2min
+    def __call__(self):
+      if iname not in hyper.ListInstances():
+        return
 
 
-  time.sleep(1)
-  for dummy in range(11):
-    if instance.name not in GetInstanceList([hv_name]):
-      break
-    time.sleep(10)
-  else:
+      try:
+        hyper.StopInstance(instance, retry=self.tried_once)
+      except errors.HypervisorError, err:
+        if iname not in hyper.ListInstances():
+          # if the instance is no longer existing, consider this a
+          # success and go to cleanup
+          return
+
+        _Fail("Failed to stop instance %s: %s", iname, err)
+
+      self.tried_once = True
+
+      raise utils.RetryAgain()
+
+  try:
+    utils.Retry(_TryShutdown(), 5, timeout)
+  except utils.RetryTimeout:
     # the shutdown did not succeed
     # the shutdown did not succeed
-    logging.error("Shutdown of '%s' unsuccessful, using destroy",
-                  instance.name)
+    logging.error("Shutdown of '%s' unsuccessful, forcing", iname)
 
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
 
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
-      _Fail("Failed to force stop instance %s: %s", instance.name, err)
+      if iname in hyper.ListInstances():
+        # only raise an error if the instance still exists, otherwise
+        # the error could simply be "instance ... unknown"!
+        _Fail("Failed to force stop instance %s: %s", iname, err)
 
     time.sleep(1)
 
     time.sleep(1)
-    if instance.name in GetInstanceList([hv_name]):
-      _Fail("Could not shutdown instance %s even by destroy", instance.name)
 
 
-  _RemoveBlockDevLinks(instance.name, instance.disks)
+    if iname in hyper.ListInstances():
+      _Fail("Could not shutdown instance %s even by destroy", iname)
 
 
-  return (True, "Instance has been shutdown successfully")
+  _RemoveBlockDevLinks(iname, instance.disks)
 
 
 
 
-def InstanceReboot(instance, reboot_type):
+def InstanceReboot(instance, reboot_type, shutdown_timeout):
   """Reboot an instance.
 
   @type instance: L{objects.Instance}
   """Reboot an instance.
 
   @type instance: L{objects.Instance}
@@ -985,11 +1099,13 @@ def InstanceReboot(instance, reboot_type):
         instance OS, do not recreate the VM
       - L{constants.INSTANCE_REBOOT_HARD}: tear down and
         restart the VM (at the hypervisor level)
         instance OS, do not recreate the VM
       - L{constants.INSTANCE_REBOOT_HARD}: tear down and
         restart the VM (at the hypervisor level)
-      - the other reboot type (L{constants.INSTANCE_REBOOT_HARD})
-        is not accepted here, since that mode is handled
-        differently
-  @rtype: boolean
-  @return: the success of the operation
+      - the other reboot type (L{constants.INSTANCE_REBOOT_FULL}) is
+        not accepted here, since that mode is handled differently, in
+        cmdlib, and translates into full stop and start of the
+        instance (instead of a call_instance_reboot RPC)
+  @type shutdown_timeout: integer
+  @param shutdown_timeout: maximum timeout for soft shutdown
+  @rtype: None
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
@@ -1005,17 +1121,13 @@ def InstanceReboot(instance, reboot_type):
       _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
       _Fail("Failed to soft reboot instance %s: %s", instance.name, err)
   elif reboot_type == constants.INSTANCE_REBOOT_HARD:
     try:
-      stop_result = InstanceShutdown(instance)
-      if not stop_result[0]:
-        return stop_result
+      InstanceShutdown(instance, shutdown_timeout)
       return StartInstance(instance)
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
   else:
     _Fail("Invalid reboot_type received: %s", reboot_type)
 
       return StartInstance(instance)
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
   else:
     _Fail("Invalid reboot_type received: %s", reboot_type)
 
-  return (True, "Reboot successful")
-
 
 def MigrationInfo(instance):
   """Gather information about an instance to be migrated.
 
 def MigrationInfo(instance):
   """Gather information about an instance to be migrated.
@@ -1029,7 +1141,7 @@ def MigrationInfo(instance):
     info = hyper.MigrationInfo(instance)
   except errors.HypervisorError, err:
     _Fail("Failed to fetch migration information: %s", err, exc=True)
     info = hyper.MigrationInfo(instance)
   except errors.HypervisorError, err:
     _Fail("Failed to fetch migration information: %s", err, exc=True)
-  return (True, info)
+  return info
 
 
 def AcceptInstance(instance, info, target):
 
 
 def AcceptInstance(instance, info, target):
@@ -1048,7 +1160,6 @@ def AcceptInstance(instance, info, target):
     hyper.AcceptInstance(instance, info, target)
   except errors.HypervisorError, err:
     _Fail("Failed to accept instance: %s", err, exc=True)
     hyper.AcceptInstance(instance, info, target)
   except errors.HypervisorError, err:
     _Fail("Failed to accept instance: %s", err, exc=True)
-  return (True, "Accept successfull")
 
 
 def FinalizeMigration(instance, info, success):
 
 
 def FinalizeMigration(instance, info, success):
@@ -1067,7 +1178,6 @@ def FinalizeMigration(instance, info, success):
     hyper.FinalizeMigration(instance, info, success)
   except errors.HypervisorError, err:
     _Fail("Failed to finalize migration: %s", err, exc=True)
     hyper.FinalizeMigration(instance, info, success)
   except errors.HypervisorError, err:
     _Fail("Failed to finalize migration: %s", err, exc=True)
-  return (True, "Migration Finalized")
 
 
 def MigrateInstance(instance, target, live):
 
 
 def MigrateInstance(instance, target, live):
@@ -1089,10 +1199,9 @@ def MigrateInstance(instance, target, live):
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   try:
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   try:
-    hyper.MigrateInstance(instance.name, target, live)
+    hyper.MigrateInstance(instance, target, live)
   except errors.HypervisorError, err:
     _Fail("Failed to migrate instance: %s", err, exc=True)
   except errors.HypervisorError, err:
     _Fail("Failed to migrate instance: %s", err, exc=True)
-  return (True, "Migration successfull")
 
 
 def BlockdevCreate(disk, size, owner, on_primary, info):
 
 
 def BlockdevCreate(disk, size, owner, on_primary, info):
@@ -1116,6 +1225,8 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
       it's not required to return anything.
 
   """
       it's not required to return anything.
 
   """
+  # TODO: remove the obsolete 'size' argument
+  # pylint: disable-msg=W0613
   clist = []
   if disk.children:
     for child in disk.children:
   clist = []
   if disk.children:
     for child in disk.children:
@@ -1127,13 +1238,14 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
         # we need the children open in case the device itself has to
         # be assembled
         try:
         # we need the children open in case the device itself has to
         # be assembled
         try:
+          # pylint: disable-msg=E1103
           crdev.Open()
         except errors.BlockDeviceError, err:
           _Fail("Can't make child '%s' read-write: %s", child, err)
       clist.append(crdev)
 
   try:
           crdev.Open()
         except errors.BlockDeviceError, err:
           _Fail("Can't make child '%s' read-write: %s", child, err)
       clist.append(crdev)
 
   try:
-    device = bdev.Create(disk.dev_type, disk.physical_id, clist, size)
+    device = bdev.Create(disk.dev_type, disk.physical_id, clist, disk.size)
   except errors.BlockDeviceError, err:
     _Fail("Can't create block device: %s", err)
 
   except errors.BlockDeviceError, err:
     _Fail("Can't create block device: %s", err)
 
@@ -1153,8 +1265,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
 
   device.SetInfo(info)
 
 
   device.SetInfo(info)
 
-  physical_id = device.unique_id
-  return True, physical_id
+  return device.unique_id
 
 
 def BlockdevRemove(disk):
 
 
 def BlockdevRemove(disk):
@@ -1169,7 +1280,6 @@ def BlockdevRemove(disk):
 
   """
   msgs = []
 
   """
   msgs = []
-  result = True
   try:
     rdev = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
   try:
     rdev = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
@@ -1182,22 +1292,19 @@ def BlockdevRemove(disk):
       rdev.Remove()
     except errors.BlockDeviceError, err:
       msgs.append(str(err))
       rdev.Remove()
     except errors.BlockDeviceError, err:
       msgs.append(str(err))
-      result = False
-    if result:
+    if not msgs:
       DevCacheManager.RemoveCache(r_path)
 
   if disk.children:
     for child in disk.children:
       DevCacheManager.RemoveCache(r_path)
 
   if disk.children:
     for child in disk.children:
-      c_status, c_msg = BlockdevRemove(child)
-      result = result and c_status
-      if c_msg: # not an empty message
-        msgs.append(c_msg)
+      try:
+        BlockdevRemove(child)
+      except RPCFail, err:
+        msgs.append(str(err))
 
 
-  if not result:
+  if msgs:
     _Fail("; ".join(msgs))
 
     _Fail("; ".join(msgs))
 
-  return True, None
-
 
 def _RecursiveAssembleBD(disk, owner, as_primary):
   """Activate a block device for an instance.
 
 def _RecursiveAssembleBD(disk, owner, as_primary):
   """Activate a block device for an instance.
@@ -1240,7 +1347,7 @@ def _RecursiveAssembleBD(disk, owner, as_primary):
       children.append(cdev)
 
   if as_primary or disk.AssembleOnSecondary():
       children.append(cdev)
 
   if as_primary or disk.AssembleOnSecondary():
-    r_dev = bdev.Assemble(disk.dev_type, disk.physical_id, children)
+    r_dev = bdev.Assemble(disk.dev_type, disk.physical_id, children, disk.size)
     r_dev.SetSyncSpeed(constants.SYNC_SPEED)
     result = r_dev
     if as_primary or disk.OpenOnSecondary():
     r_dev.SetSyncSpeed(constants.SYNC_SPEED)
     result = r_dev
     if as_primary or disk.OpenOnSecondary():
@@ -1266,17 +1373,18 @@ def BlockdevAssemble(disk, owner, as_primary):
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
+      # pylint: disable-msg=E1103
       result = result.dev_path
   except errors.BlockDeviceError, err:
     _Fail("Error while assembling disk: %s", err, exc=True)
 
       result = result.dev_path
   except errors.BlockDeviceError, err:
     _Fail("Error while assembling disk: %s", err, exc=True)
 
-  return True, result
+  return result
 
 
 def BlockdevShutdown(disk):
   """Shut down a block device.
 
 
 
 def BlockdevShutdown(disk):
   """Shut down a block device.
 
-  First, if the device is assembled (Attach() is successfull), then
+  First, if the device is assembled (Attach() is successful), then
   the device is shutdown. Then the children of the device are
   shutdown.
 
   the device is shutdown. Then the children of the device are
   shutdown.
 
@@ -1287,12 +1395,10 @@ def BlockdevShutdown(disk):
   @type disk: L{objects.Disk}
   @param disk: the description of the disk we should
       shutdown
   @type disk: L{objects.Disk}
   @param disk: the description of the disk we should
       shutdown
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: None
 
   """
   msgs = []
 
   """
   msgs = []
-  result = True
   r_dev = _RecursiveFindBD(disk)
   if r_dev is not None:
     r_path = r_dev.dev_path
   r_dev = _RecursiveFindBD(disk)
   if r_dev is not None:
     r_path = r_dev.dev_path
@@ -1301,18 +1407,16 @@ def BlockdevShutdown(disk):
       DevCacheManager.RemoveCache(r_path)
     except errors.BlockDeviceError, err:
       msgs.append(str(err))
       DevCacheManager.RemoveCache(r_path)
     except errors.BlockDeviceError, err:
       msgs.append(str(err))
-      result = False
 
   if disk.children:
     for child in disk.children:
 
   if disk.children:
     for child in disk.children:
-      c_status, c_msg = BlockdevShutdown(child)
-      result = result and c_status
-      if c_msg: # not an empty message
-        msgs.append(c_msg)
+      try:
+        BlockdevShutdown(child)
+      except RPCFail, err:
+        msgs.append(str(err))
 
 
-  if not result:
+  if msgs:
     _Fail("; ".join(msgs))
     _Fail("; ".join(msgs))
-  return (True, None)
 
 
 def BlockdevAddchildren(parent_cdev, new_cdevs):
 
 
 def BlockdevAddchildren(parent_cdev, new_cdevs):
@@ -1322,8 +1426,7 @@ def BlockdevAddchildren(parent_cdev, new_cdevs):
   @param parent_cdev: the disk to which we should add children
   @type new_cdevs: list of L{objects.Disk}
   @param new_cdevs: the list of children which we should add
   @param parent_cdev: the disk to which we should add children
   @type new_cdevs: list of L{objects.Disk}
   @param new_cdevs: the list of children which we should add
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: None
 
   """
   parent_bdev = _RecursiveFindBD(parent_cdev)
 
   """
   parent_bdev = _RecursiveFindBD(parent_cdev)
@@ -1333,7 +1436,6 @@ def BlockdevAddchildren(parent_cdev, new_cdevs):
   if new_bdevs.count(None) > 0:
     _Fail("Can't find new device(s) to add: %s:%s", new_bdevs, new_cdevs)
   parent_bdev.AddChildren(new_bdevs)
   if new_bdevs.count(None) > 0:
     _Fail("Can't find new device(s) to add: %s:%s", new_bdevs, new_cdevs)
   parent_bdev.AddChildren(new_bdevs)
-  return (True, None)
 
 
 def BlockdevRemovechildren(parent_cdev, new_cdevs):
 
 
 def BlockdevRemovechildren(parent_cdev, new_cdevs):
@@ -1343,8 +1445,7 @@ def BlockdevRemovechildren(parent_cdev, new_cdevs):
   @param parent_cdev: the disk from which we should remove children
   @type new_cdevs: list of L{objects.Disk}
   @param new_cdevs: the list of children which we should remove
   @param parent_cdev: the disk from which we should remove children
   @type new_cdevs: list of L{objects.Disk}
   @param new_cdevs: the list of children which we should remove
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: None
 
   """
   parent_bdev = _RecursiveFindBD(parent_cdev)
 
   """
   parent_bdev = _RecursiveFindBD(parent_cdev)
@@ -1360,9 +1461,10 @@ def BlockdevRemovechildren(parent_cdev, new_cdevs):
       else:
         devs.append(bd.dev_path)
     else:
       else:
         devs.append(bd.dev_path)
     else:
+      if not utils.IsNormAbsPath(rpath):
+        _Fail("Strange path returned from StaticDevPath: '%s'", rpath)
       devs.append(rpath)
   parent_bdev.RemoveChildren(devs)
       devs.append(rpath)
   parent_bdev.RemoveChildren(devs)
-  return (True, None)
 
 
 def BlockdevGetmirrorstatus(disks):
 
 
 def BlockdevGetmirrorstatus(disks):
@@ -1383,14 +1485,16 @@ def BlockdevGetmirrorstatus(disks):
     rbd = _RecursiveFindBD(dsk)
     if rbd is None:
       _Fail("Can't find device %s", dsk)
     rbd = _RecursiveFindBD(dsk)
     if rbd is None:
       _Fail("Can't find device %s", dsk)
+
     stats.append(rbd.CombinedSyncStatus())
     stats.append(rbd.CombinedSyncStatus())
-  return True, stats
+
+  return stats
 
 
 def _RecursiveFindBD(disk):
   """Check if a device is activated.
 
 
 
 def _RecursiveFindBD(disk):
   """Check if a device is activated.
 
-  If so, return informations about the real device.
+  If so, return information about the real device.
 
   @type disk: L{objects.Disk}
   @param disk: the disk object we need to find
 
   @type disk: L{objects.Disk}
   @param disk: the disk object we need to find
@@ -1404,29 +1508,104 @@ def _RecursiveFindBD(disk):
     for chdisk in disk.children:
       children.append(_RecursiveFindBD(chdisk))
 
     for chdisk in disk.children:
       children.append(_RecursiveFindBD(chdisk))
 
-  return bdev.FindDevice(disk.dev_type, disk.physical_id, children)
+  return bdev.FindDevice(disk.dev_type, disk.physical_id, children, disk.size)
 
 
 def BlockdevFind(disk):
   """Check if a device is activated.
 
 
 
 def BlockdevFind(disk):
   """Check if a device is activated.
 
-  If it is, return informations about the real device.
+  If it is, return information about the real device.
 
   @type disk: L{objects.Disk}
   @param disk: the disk to find
 
   @type disk: L{objects.Disk}
   @param disk: the disk to find
-  @rtype: None or tuple
-  @return: None if the disk cannot be found, otherwise a
-      tuple (device_path, major, minor, sync_percent,
-      estimated_time, is_degraded)
+  @rtype: None or objects.BlockDevStatus
+  @return: None if the disk cannot be found, otherwise a the current
+           information
 
   """
   try:
     rbd = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
     _Fail("Failed to find device: %s", err, exc=True)
 
   """
   try:
     rbd = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
     _Fail("Failed to find device: %s", err, exc=True)
+
   if rbd is None:
   if rbd is None:
-    return (True, None)
-  return (True, (rbd.dev_path, rbd.major, rbd.minor) + rbd.GetSyncStatus())
+    return None
+
+  return rbd.GetSyncStatus()
+
+
+def BlockdevGetsize(disks):
+  """Computes the size of the given disks.
+
+  If a disk is not found, returns None instead.
+
+  @type disks: list of L{objects.Disk}
+  @param disks: the list of disk to compute the size for
+  @rtype: list
+  @return: list with elements None if the disk cannot be found,
+      otherwise the size
+
+  """
+  result = []
+  for cf in disks:
+    try:
+      rbd = _RecursiveFindBD(cf)
+    except errors.BlockDeviceError:
+      result.append(None)
+      continue
+    if rbd is None:
+      result.append(None)
+    else:
+      result.append(rbd.GetActualSize())
+  return result
+
+
+def BlockdevExport(disk, dest_node, dest_path, cluster_name):
+  """Export a block device to a remote node.
+
+  @type disk: L{objects.Disk}
+  @param disk: the description of the disk to export
+  @type dest_node: str
+  @param dest_node: the destination node to export to
+  @type dest_path: str
+  @param dest_path: the destination path on the target node
+  @type cluster_name: str
+  @param cluster_name: the cluster name, needed for SSH hostalias
+  @rtype: None
+
+  """
+  real_disk = _RecursiveFindBD(disk)
+  if real_disk is None:
+    _Fail("Block device '%s' is not set up", disk)
+
+  real_disk.Open()
+
+  # the block size on the read dd is 1MiB to match our units
+  expcmd = utils.BuildShellCmd("set -e; set -o pipefail; "
+                               "dd if=%s bs=1048576 count=%s",
+                               real_disk.dev_path, str(disk.size))
+
+  # we set here a smaller block size as, due to ssh buffering, more
+  # than 64-128k will mostly ignored; we use nocreat to fail if the
+  # device is not already there or we pass a wrong path; we use
+  # notrunc to no attempt truncate on an LV device; we use oflag=dsync
+  # to not buffer too much memory; this means that at best, we flush
+  # every 64k, which will not be very fast
+  destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536"
+                                " oflag=dsync", dest_path)
+
+  remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node,
+                                                   constants.GANETI_RUNAS,
+                                                   destcmd)
+
+  # all commands have been checked, so we're safe to combine them
+  command = '|'.join([expcmd, utils.ShellQuoteArgs(remotecmd)])
+
+  result = utils.RunCmd(["bash", "-c", command])
+
+  if result.failed:
+    _Fail("Disk copy command '%s' returned error: %s"
+          " output: %s", command, result.fail_reason, result.output)
 
 
 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
 
 
 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
@@ -1449,28 +1628,13 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
   @param atime: the atime to set on the file (can be None)
   @type mtime: float
   @param mtime: the mtime to set on the file (can be None)
   @param atime: the atime to set on the file (can be None)
   @type mtime: float
   @param mtime: the mtime to set on the file (can be None)
-  @rtype: boolean
-  @return: the success of the operation; errors are logged
-      in the node daemon log
+  @rtype: None
 
   """
   if not os.path.isabs(file_name):
     _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
 
 
   """
   if not os.path.isabs(file_name):
     _Fail("Filename passed to UploadFile is not absolute: '%s'", file_name)
 
-  allowed_files = set([
-    constants.CLUSTER_CONF_FILE,
-    constants.ETC_HOSTS,
-    constants.SSH_KNOWN_HOSTS_FILE,
-    constants.VNC_PASSWORD_FILE,
-    constants.RAPI_CERT_FILE,
-    constants.RAPI_USERS_FILE,
-    ])
-
-  for hv_name in constants.HYPER_TYPES:
-    hv_class = hypervisor.GetHypervisor(hv_name)
-    allowed_files.update(hv_class.GetAncillaryFiles())
-
-  if file_name not in allowed_files:
+  if file_name not in _ALLOWED_UPLOAD_FILES:
     _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
           file_name)
 
     _Fail("Filename passed to UploadFile not in allowed upload targets: '%s'",
           file_name)
 
@@ -1478,7 +1642,6 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
 
   utils.WriteFile(file_name, data=raw_data, mode=mode, uid=uid, gid=gid,
                   atime=atime, mtime=mtime)
 
   utils.WriteFile(file_name, data=raw_data, mode=mode, uid=uid, gid=gid,
                   atime=atime, mtime=mtime)
-  return (True, "success")
 
 
 def WriteSsconfFiles(values):
 
 
 def WriteSsconfFiles(values):
@@ -1488,7 +1651,6 @@ def WriteSsconfFiles(values):
 
   """
   ssconf.SimpleStore().WriteFiles(values)
 
   """
   ssconf.SimpleStore().WriteFiles(values)
-  return True, None
 
 
 def _ErrnoOrStr(err):
 
 
 def _ErrnoOrStr(err):
@@ -1509,46 +1671,39 @@ def _ErrnoOrStr(err):
   return detail
 
 
   return detail
 
 
-def _OSOndiskVersion(name, os_dir):
+def _OSOndiskAPIVersion(os_dir):
   """Compute and return the API version of a given OS.
 
   """Compute and return the API version of a given OS.
 
-  This function will try to read the API version of the OS given by
-  the 'name' parameter and residing in the 'os_dir' directory.
+  This function will try to read the API version of the OS residing in
+  the 'os_dir' directory.
 
 
-  @type name: str
-  @param name: the OS name we should look for
   @type os_dir: str
   @type os_dir: str
-  @param os_dir: the directory inwhich we should look for the OS
+  @param os_dir: the directory in which we should look for the OS
   @rtype: tuple
   @return: tuple (status, data) with status denoting the validity and
       data holding either the vaid versions or an error message
 
   """
   @rtype: tuple
   @return: tuple (status, data) with status denoting the validity and
       data holding either the vaid versions or an error message
 
   """
-  api_file = os.path.sep.join([os_dir, "ganeti_api_version"])
+  api_file = utils.PathJoin(os_dir, constants.OS_API_FILE)
 
   try:
     st = os.stat(api_file)
   except EnvironmentError, err:
 
   try:
     st = os.stat(api_file)
   except EnvironmentError, err:
-    return False, ("Required file 'ganeti_api_version' file not"
-                   " found under path %s: %s" % (os_dir, _ErrnoOrStr(err)))
+    return False, ("Required file '%s' not found under path %s: %s" %
+                   (constants.OS_API_FILE, os_dir, _ErrnoOrStr(err)))
 
   if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
 
   if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
-    return False, ("File 'ganeti_api_version' file at %s is not"
-                   " a regular file" % os_dir)
+    return False, ("File '%s' in %s is not a regular file" %
+                   (constants.OS_API_FILE, os_dir))
 
   try:
 
   try:
-    f = open(api_file)
-    try:
-      api_versions = f.readlines()
-    finally:
-      f.close()
+    api_versions = utils.ReadFile(api_file).splitlines()
   except EnvironmentError, err:
     return False, ("Error while reading the API version file at %s: %s" %
                    (api_file, _ErrnoOrStr(err)))
 
   except EnvironmentError, err:
     return False, ("Error while reading the API version file at %s: %s" %
                    (api_file, _ErrnoOrStr(err)))
 
-  api_versions = [version.strip() for version in api_versions]
   try:
   try:
-    api_versions = [int(version) for version in api_versions]
+    api_versions = [int(version.strip()) for version in api_versions]
   except (TypeError, ValueError), err:
     return False, ("API version(s) can't be converted to integer: %s" %
                    str(err))
   except (TypeError, ValueError), err:
     return False, ("API version(s) can't be converted to integer: %s" %
                    str(err))
@@ -1564,12 +1719,13 @@ def DiagnoseOS(top_dirs=None):
       search (if not given defaults to
       L{constants.OS_SEARCH_PATH})
   @rtype: list of L{objects.OS}
       search (if not given defaults to
       L{constants.OS_SEARCH_PATH})
   @rtype: list of L{objects.OS}
-  @return: a list of tuples (name, path, status, diagnose)
+  @return: a list of tuples (name, path, status, diagnose, variants)
       for all (potential) OSes under all search paths, where:
           - name is the (potential) OS name
           - path is the full path to the OS
           - status True/False is the validity of the OS
           - diagnose is the error message for an invalid OS, otherwise empty
       for all (potential) OSes under all search paths, where:
           - name is the (potential) OS name
           - path is the full path to the OS
           - status True/False is the validity of the OS
           - diagnose is the error message for an invalid OS, otherwise empty
+          - variants is a list of supported OS variants, if any
 
   """
   if top_dirs is None:
 
   """
   if top_dirs is None:
@@ -1581,18 +1737,20 @@ def DiagnoseOS(top_dirs=None):
       try:
         f_names = utils.ListVisibleFiles(dir_name)
       except EnvironmentError, err:
       try:
         f_names = utils.ListVisibleFiles(dir_name)
       except EnvironmentError, err:
-        logging.exception("Can't list the OS directory %s", dir_name)
+        logging.exception("Can't list the OS directory %s: %s", dir_name, err)
         break
       for name in f_names:
         break
       for name in f_names:
-        os_path = os.path.sep.join([dir_name, name])
+        os_path = utils.PathJoin(dir_name, name)
         status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
         if status:
           diagnose = ""
         status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
         if status:
           diagnose = ""
+          variants = os_inst.supported_variants
         else:
           diagnose = os_inst
         else:
           diagnose = os_inst
-        result.append((name, os_path, status, diagnose))
+          variants = []
+        result.append((name, os_path, status, diagnose, variants))
 
 
-  return True, result
+  return result
 
 
 def _TryOSFromDisk(name, base_dir=None):
 
 
 def _TryOSFromDisk(name, base_dir=None):
@@ -1611,45 +1769,62 @@ def _TryOSFromDisk(name, base_dir=None):
   """
   if base_dir is None:
     os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
   """
   if base_dir is None:
     os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
-    if os_dir is None:
-      return False, "Directory for OS %s not found in search path" % name
   else:
   else:
-    os_dir = os.path.sep.join([base_dir, name])
+    os_dir = utils.FindFile(name, [base_dir], os.path.isdir)
 
 
-  status, api_versions = _OSOndiskVersion(name, os_dir)
+  if os_dir is None:
+    return False, "Directory for OS %s not found in search path" % name
+
+  status, api_versions = _OSOndiskAPIVersion(os_dir)
   if not status:
     # push the error up
     return status, api_versions
 
   if not status:
     # push the error up
     return status, api_versions
 
-  if constants.OS_API_VERSION not in api_versions:
+  if not constants.OS_API_VERSIONS.intersection(api_versions):
     return False, ("API version mismatch for path '%s': found %s, want %s." %
     return False, ("API version mismatch for path '%s': found %s, want %s." %
-                   (os_dir, api_versions, constants.OS_API_VERSION))
+                   (os_dir, api_versions, constants.OS_API_VERSIONS))
+
+  # OS Files dictionary, we will populate it with the absolute path names
+  os_files = dict.fromkeys(constants.OS_SCRIPTS)
 
 
-  # OS Scripts dictionary, we will populate it with the actual script names
-  os_scripts = dict.fromkeys(constants.OS_SCRIPTS)
+  if max(api_versions) >= constants.OS_API_V15:
+    os_files[constants.OS_VARIANTS_FILE] = ''
 
 
-  for script in os_scripts:
-    os_scripts[script] = os.path.sep.join([os_dir, script])
+  for filename in os_files:
+    os_files[filename] = utils.PathJoin(os_dir, filename)
 
     try:
 
     try:
-      st = os.stat(os_scripts[script])
+      st = os.stat(os_files[filename])
     except EnvironmentError, err:
     except EnvironmentError, err:
-      return False, ("Script '%s' under path '%s' is missing (%s)" %
-                     (script, os_dir, _ErrnoOrStr(err)))
-
-    if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
-      return False, ("Script '%s' under path '%s' is not executable" %
-                     (script, os_dir))
+      return False, ("File '%s' under path '%s' is missing (%s)" %
+                     (filename, os_dir, _ErrnoOrStr(err)))
 
     if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
 
     if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
-      return False, ("Script '%s' under path '%s' is not a regular file" %
-                     (script, os_dir))
+      return False, ("File '%s' under path '%s' is not a regular file" %
+                     (filename, os_dir))
+
+    if filename in constants.OS_SCRIPTS:
+      if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
+        return False, ("File '%s' under path '%s' is not executable" %
+                       (filename, os_dir))
+
+  variants = None
+  if constants.OS_VARIANTS_FILE in os_files:
+    variants_file = os_files[constants.OS_VARIANTS_FILE]
+    try:
+      variants = utils.ReadFile(variants_file).splitlines()
+    except EnvironmentError, err:
+      return False, ("Error while reading the OS variants file at %s: %s" %
+                     (variants_file, _ErrnoOrStr(err)))
+    if not variants:
+      return False, ("No supported os variant found")
 
   os_obj = objects.OS(name=name, path=os_dir,
 
   os_obj = objects.OS(name=name, path=os_dir,
-                      create_script=os_scripts[constants.OS_SCRIPT_CREATE],
-                      export_script=os_scripts[constants.OS_SCRIPT_EXPORT],
-                      import_script=os_scripts[constants.OS_SCRIPT_IMPORT],
-                      rename_script=os_scripts[constants.OS_SCRIPT_RENAME],
+                      create_script=os_files[constants.OS_SCRIPT_CREATE],
+                      export_script=os_files[constants.OS_SCRIPT_EXPORT],
+                      import_script=os_files[constants.OS_SCRIPT_IMPORT],
+                      rename_script=os_files[constants.OS_SCRIPT_RENAME],
+                      supported_variants=variants,
                       api_versions=api_versions)
   return True, os_obj
 
                       api_versions=api_versions)
   return True, os_obj
 
@@ -1672,7 +1847,8 @@ def OSFromDisk(name, base_dir=None):
   @raise RPCFail: if we don't find a valid OS
 
   """
   @raise RPCFail: if we don't find a valid OS
 
   """
-  status, payload = _TryOSFromDisk(name, base_dir)
+  name_only = name.split("+", 1)[0]
+  status, payload = _TryOSFromDisk(name_only, base_dir)
 
   if not status:
     _Fail(payload)
 
   if not status:
     _Fail(payload)
@@ -1680,11 +1856,13 @@ def OSFromDisk(name, base_dir=None):
   return payload
 
 
   return payload
 
 
-def OSEnvironment(instance, debug=0):
+def OSEnvironment(instance, inst_os, debug=0):
   """Calculate the environment for an os script.
 
   @type instance: L{objects.Instance}
   @param instance: target instance for the os script run
   """Calculate the environment for an os script.
 
   @type instance: L{objects.Instance}
   @param instance: target instance for the os script run
+  @type inst_os: L{objects.OS}
+  @param inst_os: operating system for which the environment is being built
   @type debug: integer
   @param debug: debug level (0 or 1, for OS Api 10)
   @rtype: dict
   @type debug: integer
   @param debug: debug level (0 or 1, for OS Api 10)
   @rtype: dict
@@ -1694,13 +1872,21 @@ def OSEnvironment(instance, debug=0):
 
   """
   result = {}
 
   """
   result = {}
-  result['OS_API_VERSION'] = '%d' % constants.OS_API_VERSION
+  api_version = \
+    max(constants.OS_API_VERSIONS.intersection(inst_os.api_versions))
+  result['OS_API_VERSION'] = '%d' % api_version
   result['INSTANCE_NAME'] = instance.name
   result['INSTANCE_OS'] = instance.os
   result['HYPERVISOR'] = instance.hypervisor
   result['DISK_COUNT'] = '%d' % len(instance.disks)
   result['NIC_COUNT'] = '%d' % len(instance.nics)
   result['DEBUG_LEVEL'] = '%d' % debug
   result['INSTANCE_NAME'] = instance.name
   result['INSTANCE_OS'] = instance.os
   result['HYPERVISOR'] = instance.hypervisor
   result['DISK_COUNT'] = '%d' % len(instance.disks)
   result['NIC_COUNT'] = '%d' % len(instance.nics)
   result['DEBUG_LEVEL'] = '%d' % debug
+  if api_version >= constants.OS_API_V15:
+    try:
+      variant = instance.os.split('+', 1)[1]
+    except IndexError:
+      variant = inst_os.supported_variants[0]
+    result['OS_VARIANT'] = variant
   for idx, disk in enumerate(instance.disks):
     real_disk = _RecursiveFindBD(disk)
     if real_disk is None:
   for idx, disk in enumerate(instance.disks):
     real_disk = _RecursiveFindBD(disk)
     if real_disk is None:
@@ -1730,6 +1916,10 @@ def OSEnvironment(instance, debug=0):
       result['NIC_%d_FRONTEND_TYPE' % idx] = \
         instance.hvparams[constants.HV_NIC_TYPE]
 
       result['NIC_%d_FRONTEND_TYPE' % idx] = \
         instance.hvparams[constants.HV_NIC_TYPE]
 
+  for source, kind in [(instance.beparams, "BE"), (instance.hvparams, "HV")]:
+    for key, value in source.items():
+      result["INSTANCE_%s_%s" % (kind, key)] = str(value)
+
   return result
 
 def BlockdevGrow(disk, amount):
   return result
 
 def BlockdevGrow(disk, amount):
@@ -1755,8 +1945,6 @@ def BlockdevGrow(disk, amount):
   except errors.BlockDeviceError, err:
     _Fail("Failed to grow block device: %s", err, exc=True)
 
   except errors.BlockDeviceError, err:
     _Fail("Failed to grow block device: %s", err, exc=True)
 
-  return True, None
-
 
 def BlockdevSnapshot(disk):
   """Create a snapshot copy of a block device.
 
 def BlockdevSnapshot(disk):
   """Create a snapshot copy of a block device.
@@ -1770,21 +1958,17 @@ def BlockdevSnapshot(disk):
   @return: snapshot disk path
 
   """
   @return: snapshot disk path
 
   """
-  if disk.children:
-    if len(disk.children) == 1:
-      # only one child, let's recurse on it
-      return BlockdevSnapshot(disk.children[0])
-    else:
-      # more than one child, choose one that matches
-      for child in disk.children:
-        if child.size == disk.size:
-          # return implies breaking the loop
-          return BlockdevSnapshot(child)
+  if disk.dev_type == constants.LD_DRBD8:
+    if not disk.children:
+      _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
+            disk.unique_id)
+    return BlockdevSnapshot(disk.children[0])
   elif disk.dev_type == constants.LD_LV:
     r_dev = _RecursiveFindBD(disk)
     if r_dev is not None:
   elif disk.dev_type == constants.LD_LV:
     r_dev = _RecursiveFindBD(disk)
     if r_dev is not None:
+      # FIXME: choose a saner value for the snapshot size
       # let's stay on the safe side and ask for the full size, for now
       # let's stay on the safe side and ask for the full size, for now
-      return True, r_dev.Snapshot(disk.size)
+      return r_dev.Snapshot(disk.size)
     else:
       _Fail("Cannot find block device %s", disk)
   else:
     else:
       _Fail("Cannot find block device %s", disk)
   else:
@@ -1792,7 +1976,7 @@ def BlockdevSnapshot(disk):
           disk.unique_id, disk.dev_type)
 
 
           disk.unique_id, disk.dev_type)
 
 
-def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
+def ExportSnapshot(disk, dest_node, instance, cluster_name, idx, debug):
   """Export a block device snapshot to a remote node.
 
   @type disk: L{objects.Disk}
   """Export a block device snapshot to a remote node.
 
   @type disk: L{objects.Disk}
@@ -1806,17 +1990,17 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
   @type idx: int
   @param idx: the index of the disk in the instance's disk list,
       used to export to the OS scripts environment
   @type idx: int
   @param idx: the index of the disk in the instance's disk list,
       used to export to the OS scripts environment
-  @rtype: boolean
-  @return: the success of the operation
+  @type debug: integer
+  @param debug: debug level, passed to the OS scripts
+  @rtype: None
 
   """
 
   """
-  export_env = OSEnvironment(instance)
-
   inst_os = OSFromDisk(instance.os)
   inst_os = OSFromDisk(instance.os)
+  export_env = OSEnvironment(instance, inst_os, debug)
+
   export_script = inst_os.export_script
 
   export_script = inst_os.export_script
 
-  logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
-                                     instance.name, int(time.time()))
+  logfile = _InstanceLogName("export", inst_os.name, instance.name)
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
   real_disk = _RecursiveFindBD(disk)
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
   real_disk = _RecursiveFindBD(disk)
@@ -1828,19 +2012,19 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
   export_env['EXPORT_DEVICE'] = real_disk.dev_path
   export_env['EXPORT_INDEX'] = str(idx)
 
   export_env['EXPORT_DEVICE'] = real_disk.dev_path
   export_env['EXPORT_INDEX'] = str(idx)
 
-  destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
+  destdir = utils.PathJoin(constants.EXPORT_DIR, instance.name + ".new")
   destfile = disk.physical_id[1]
 
   # the target command is built out of three individual commands,
   # which are joined by pipes; we check each individual command for
   # valid parameters
   destfile = disk.physical_id[1]
 
   # the target command is built out of three individual commands,
   # which are joined by pipes; we check each individual command for
   # valid parameters
-  expcmd = utils.BuildShellCmd("cd %s; %s 2>%s", inst_os.path,
-                               export_script, logfile)
+  expcmd = utils.BuildShellCmd("set -e; set -o pipefail; cd %s; %s 2>%s",
+                               inst_os.path, export_script, logfile)
 
   comprcmd = "gzip"
 
 
   comprcmd = "gzip"
 
-  destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
-                                destdir, destdir, destfile)
+  destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s",
+                                destdir, utils.PathJoin(destdir, destfile))
   remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node,
                                                    constants.GANETI_RUNAS,
                                                    destcmd)
   remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node,
                                                    constants.GANETI_RUNAS,
                                                    destcmd)
@@ -1848,14 +2032,12 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
   # all commands have been checked, so we're safe to combine them
   command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
 
   # all commands have been checked, so we're safe to combine them
   command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
 
-  result = utils.RunCmd(command, env=export_env)
+  result = utils.RunCmd(["bash", "-c", command], env=export_env)
 
   if result.failed:
     _Fail("OS snapshot export command '%s' returned error: %s"
           " output: %s", command, result.fail_reason, result.output)
 
 
   if result.failed:
     _Fail("OS snapshot export command '%s' returned error: %s"
           " output: %s", command, result.fail_reason, result.output)
 
-  return (True, None)
-
 
 def FinalizeExport(instance, snap_disks):
   """Write out the export configuration information.
 
 def FinalizeExport(instance, snap_disks):
   """Write out the export configuration information.
@@ -1867,12 +2049,11 @@ def FinalizeExport(instance, snap_disks):
   @param snap_disks: list of snapshot block devices, which
       will be used to get the actual name of the dump file
 
   @param snap_disks: list of snapshot block devices, which
       will be used to get the actual name of the dump file
 
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: None
 
   """
 
   """
-  destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
-  finaldestdir = os.path.join(constants.EXPORT_DIR, instance.name)
+  destdir = utils.PathJoin(constants.EXPORT_DIR, instance.name + ".new")
+  finaldestdir = utils.PathJoin(constants.EXPORT_DIR, instance.name)
 
   config = objects.SerializableConfigParser()
 
 
   config = objects.SerializableConfigParser()
 
@@ -1915,13 +2096,11 @@ def FinalizeExport(instance, snap_disks):
 
   config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_total)
 
 
   config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_total)
 
-  utils.WriteFile(os.path.join(destdir, constants.EXPORT_CONF_FILE),
+  utils.WriteFile(utils.PathJoin(destdir, constants.EXPORT_CONF_FILE),
                   data=config.Dumps())
   shutil.rmtree(finaldestdir, True)
   shutil.move(destdir, finaldestdir)
 
                   data=config.Dumps())
   shutil.rmtree(finaldestdir, True)
   shutil.move(destdir, finaldestdir)
 
-  return True, None
-
 
 def ExportInfo(dest):
   """Get export configuration information.
 
 def ExportInfo(dest):
   """Get export configuration information.
@@ -1934,7 +2113,7 @@ def ExportInfo(dest):
       export info
 
   """
       export info
 
   """
-  cff = os.path.join(dest, constants.EXPORT_CONF_FILE)
+  cff = utils.PathJoin(dest, constants.EXPORT_CONF_FILE)
 
   config = objects.SerializableConfigParser()
   config.read(cff)
 
   config = objects.SerializableConfigParser()
   config.read(cff)
@@ -1943,10 +2122,10 @@ def ExportInfo(dest):
       not config.has_section(constants.INISECT_INS)):
     _Fail("Export info file doesn't have the required fields")
 
       not config.has_section(constants.INISECT_INS)):
     _Fail("Export info file doesn't have the required fields")
 
-  return True, config.Dumps()
+  return config.Dumps()
 
 
 
 
-def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
+def ImportOSIntoInstance(instance, src_node, src_images, cluster_name, debug):
   """Import an os image into an instance.
 
   @type instance: L{objects.Instance}
   """Import an os image into an instance.
 
   @type instance: L{objects.Instance}
@@ -1955,16 +2134,17 @@ def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
   @param src_node: source node for the disk images
   @type src_images: list of string
   @param src_images: absolute paths of the disk images
   @param src_node: source node for the disk images
   @type src_images: list of string
   @param src_images: absolute paths of the disk images
+  @type debug: integer
+  @param debug: debug level, passed to the OS scripts
   @rtype: list of boolean
   @return: each boolean represent the success of importing the n-th disk
 
   """
   @rtype: list of boolean
   @return: each boolean represent the success of importing the n-th disk
 
   """
-  import_env = OSEnvironment(instance)
   inst_os = OSFromDisk(instance.os)
   inst_os = OSFromDisk(instance.os)
+  import_env = OSEnvironment(instance, inst_os, debug)
   import_script = inst_os.import_script
 
   import_script = inst_os.import_script
 
-  logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
-                                        instance.name, int(time.time()))
+  logfile = _InstanceLogName("import", instance.os, instance.name)
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
 
   if not os.path.exists(constants.LOG_OS_DIR):
     os.mkdir(constants.LOG_OS_DIR, 0750)
 
@@ -1992,7 +2172,6 @@ def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
 
   if final_result:
     _Fail("; ".join(final_result), log=False)
 
   if final_result:
     _Fail("; ".join(final_result), log=False)
-  return True, None
 
 
 def ListExports():
 
 
 def ListExports():
@@ -2003,7 +2182,7 @@ def ListExports():
 
   """
   if os.path.isdir(constants.EXPORT_DIR):
 
   """
   if os.path.isdir(constants.EXPORT_DIR):
-    return True, utils.ListVisibleFiles(constants.EXPORT_DIR)
+    return utils.ListVisibleFiles(constants.EXPORT_DIR)
   else:
     _Fail("No exports directory")
 
   else:
     _Fail("No exports directory")
 
@@ -2013,19 +2192,16 @@ def RemoveExport(export):
 
   @type export: str
   @param export: the name of the export to remove
 
   @type export: str
   @param export: the name of the export to remove
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: None
 
   """
 
   """
-  target = os.path.join(constants.EXPORT_DIR, export)
+  target = utils.PathJoin(constants.EXPORT_DIR, export)
 
   try:
     shutil.rmtree(target)
   except EnvironmentError, err:
     _Fail("Error while removing the export: %s", err, exc=True)
 
 
   try:
     shutil.rmtree(target)
   except EnvironmentError, err:
     _Fail("Error while removing the export: %s", err, exc=True)
 
-  return True, None
-
 
 def BlockdevRename(devlist):
   """Rename a list of block devices.
 
 def BlockdevRename(devlist):
   """Rename a list of block devices.
@@ -2066,7 +2242,6 @@ def BlockdevRename(devlist):
       result = False
   if not result:
     _Fail("; ".join(msgs))
       result = False
   if not result:
     _Fail("; ".join(msgs))
-  return True, None
 
 
 def _TransformFileStorageDir(file_storage_dir):
 
 
 def _TransformFileStorageDir(file_storage_dir):
@@ -2082,6 +2257,8 @@ def _TransformFileStorageDir(file_storage_dir):
   @return: the normalized path if valid, None otherwise
 
   """
   @return: the normalized path if valid, None otherwise
 
   """
+  if not constants.ENABLE_FILE_STORAGE:
+    _Fail("File storage disabled at configure time")
   cfg = _GetConfig()
   file_storage_dir = os.path.normpath(file_storage_dir)
   base_file_storage_dir = cfg.GetFileStorageDir()
   cfg = _GetConfig()
   file_storage_dir = os.path.normpath(file_storage_dir)
   base_file_storage_dir = cfg.GetFileStorageDir()
@@ -2114,7 +2291,6 @@ def CreateFileStorageDir(file_storage_dir):
     except OSError, err:
       _Fail("Cannot create file storage directory '%s': %s",
             file_storage_dir, err, exc=True)
     except OSError, err:
       _Fail("Cannot create file storage directory '%s': %s",
             file_storage_dir, err, exc=True)
-  return True, None
 
 
 def RemoveFileStorageDir(file_storage_dir):
 
 
 def RemoveFileStorageDir(file_storage_dir):
@@ -2126,7 +2302,7 @@ def RemoveFileStorageDir(file_storage_dir):
   @param file_storage_dir: the directory we should cleanup
   @rtype: tuple (success,)
   @return: tuple of one element, C{success}, denoting
   @param file_storage_dir: the directory we should cleanup
   @rtype: tuple (success,)
   @return: tuple of one element, C{success}, denoting
-      whether the operation was successfull
+      whether the operation was successful
 
   """
   file_storage_dir = _TransformFileStorageDir(file_storage_dir)
 
   """
   file_storage_dir = _TransformFileStorageDir(file_storage_dir)
@@ -2141,8 +2317,6 @@ def RemoveFileStorageDir(file_storage_dir):
       _Fail("Cannot remove file storage directory '%s': %s",
             file_storage_dir, err)
 
       _Fail("Cannot remove file storage directory '%s': %s",
             file_storage_dir, err)
 
-  return True, None
-
 
 def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
   """Rename the file storage directory.
 
 def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
   """Rename the file storage directory.
@@ -2172,7 +2346,6 @@ def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
     if os.path.exists(old_file_storage_dir):
       _Fail("Cannot rename '%s' to '%s': both locations exist",
             old_file_storage_dir, new_file_storage_dir)
     if os.path.exists(old_file_storage_dir):
       _Fail("Cannot rename '%s' to '%s': both locations exist",
             old_file_storage_dir, new_file_storage_dir)
-  return True, None
 
 
 def _EnsureJobQueueFile(file_name):
 
 
 def _EnsureJobQueueFile(file_name):
@@ -2211,8 +2384,6 @@ def JobQueueUpdate(file_name, content):
   # Write and replace the file atomically
   utils.WriteFile(file_name, data=_Decompress(content))
 
   # Write and replace the file atomically
   utils.WriteFile(file_name, data=_Decompress(content))
 
-  return True, None
-
 
 def JobQueueRename(old, new):
   """Renames a job queue file.
 
 def JobQueueRename(old, new):
   """Renames a job queue file.
@@ -2232,8 +2403,6 @@ def JobQueueRename(old, new):
 
   utils.RenameFile(old, new, mkdir=True)
 
 
   utils.RenameFile(old, new, mkdir=True)
 
-  return True, None
-
 
 def JobQueueSetDrainFlag(drain_flag):
   """Set the drain flag for the queue.
 
 def JobQueueSetDrainFlag(drain_flag):
   """Set the drain flag for the queue.
@@ -2252,8 +2421,6 @@ def JobQueueSetDrainFlag(drain_flag):
   else:
     utils.RemoveFile(constants.JOB_QUEUE_DRAIN_FILE)
 
   else:
     utils.RemoveFile(constants.JOB_QUEUE_DRAIN_FILE)
 
-  return True, None
-
 
 def BlockdevClose(instance_name, disks):
   """Closes the given block devices.
 
 def BlockdevClose(instance_name, disks):
   """Closes the given block devices.
@@ -2290,7 +2457,6 @@ def BlockdevClose(instance_name, disks):
   else:
     if instance_name:
       _RemoveBlockDevLinks(instance_name, disks)
   else:
     if instance_name:
       _RemoveBlockDevLinks(instance_name, disks)
-    return (True, "All devices secondary")
 
 
 def ValidateHVParams(hvname, hvparams):
 
 
 def ValidateHVParams(hvname, hvparams):
@@ -2300,17 +2466,12 @@ def ValidateHVParams(hvname, hvparams):
   @param hvname: the hypervisor name
   @type hvparams: dict
   @param hvparams: the hypervisor parameters to be validated
   @param hvname: the hypervisor name
   @type hvparams: dict
   @param hvparams: the hypervisor parameters to be validated
-  @rtype: tuple (success, message)
-  @return: a tuple of success and message, where success
-      indicates the succes of the operation, and message
-      which will contain the error details in case we
-      failed
+  @rtype: None
 
   """
   try:
     hv_type = hypervisor.GetHypervisor(hvname)
     hv_type.ValidateParameters(hvparams)
 
   """
   try:
     hv_type = hypervisor.GetHypervisor(hvname)
     hv_type.ValidateParameters(hvparams)
-    return (True, "Validation passed")
   except errors.HypervisorError, err:
     _Fail(str(err), log=False)
 
   except errors.HypervisorError, err:
     _Fail(str(err), log=False)
 
@@ -2323,16 +2484,19 @@ def DemoteFromMC():
   master, myself = ssconf.GetMasterAndMyself()
   if master == myself:
     _Fail("ssconf status shows I'm the master node, will not demote")
   master, myself = ssconf.GetMasterAndMyself()
   if master == myself:
     _Fail("ssconf status shows I'm the master node, will not demote")
-  pid_file = utils.DaemonPidFileName(constants.MASTERD_PID)
-  if utils.IsProcessAlive(utils.ReadPidFile(pid_file)):
+
+  result = utils.RunCmd([constants.DAEMON_UTIL, "check", constants.MASTERD])
+  if not result.failed:
     _Fail("The master daemon is running, will not demote")
     _Fail("The master daemon is running, will not demote")
+
   try:
   try:
-    utils.CreateBackup(constants.CLUSTER_CONF_FILE)
+    if os.path.isfile(constants.CLUSTER_CONF_FILE):
+      utils.CreateBackup(constants.CLUSTER_CONF_FILE)
   except EnvironmentError, err:
     if err.errno != errno.ENOENT:
       _Fail("Error while backing up cluster file: %s", err, exc=True)
   except EnvironmentError, err:
     if err.errno != errno.ENOENT:
       _Fail("Error while backing up cluster file: %s", err, exc=True)
+
   utils.RemoveFile(constants.CLUSTER_CONF_FILE)
   utils.RemoveFile(constants.CLUSTER_CONF_FILE)
-  return (True, "Done")
 
 
 def _FindDisks(nodes_ip, disks):
 
 
 def _FindDisks(nodes_ip, disks):
@@ -2367,7 +2531,6 @@ def DrbdDisconnectNet(nodes_ip, disks):
     except errors.BlockDeviceError, err:
       _Fail("Can't change network configuration to standalone mode: %s",
             err, exc=True)
     except errors.BlockDeviceError, err:
       _Fail("Can't change network configuration to standalone mode: %s",
             err, exc=True)
-  return (True, "All disks are now disconnected")
 
 
 def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
 
 
 def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
@@ -2389,34 +2552,40 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
       rd.AttachNet(multimaster)
     except errors.BlockDeviceError, err:
       _Fail("Can't change network configuration: %s", err)
       rd.AttachNet(multimaster)
     except errors.BlockDeviceError, err:
       _Fail("Can't change network configuration: %s", err)
+
   # wait until the disks are connected; we need to retry the re-attach
   # if the device becomes standalone, as this might happen if the one
   # node disconnects and reconnects in a different mode before the
   # other node reconnects; in this case, one or both of the nodes will
   # decide it has wrong configuration and switch to standalone
   # wait until the disks are connected; we need to retry the re-attach
   # if the device becomes standalone, as this might happen if the one
   # node disconnects and reconnects in a different mode before the
   # other node reconnects; in this case, one or both of the nodes will
   # decide it has wrong configuration and switch to standalone
-  RECONNECT_TIMEOUT = 2 * 60
-  sleep_time = 0.100 # start with 100 miliseconds
-  timeout_limit = time.time() + RECONNECT_TIMEOUT
-  while time.time() < timeout_limit:
+
+  def _Attach():
     all_connected = True
     all_connected = True
+
     for rd in bdevs:
       stats = rd.GetProcStatus()
     for rd in bdevs:
       stats = rd.GetProcStatus()
-      if not (stats.is_connected or stats.is_in_resync):
-        all_connected = False
+
+      all_connected = (all_connected and
+                       (stats.is_connected or stats.is_in_resync))
+
       if stats.is_standalone:
         # peer had different config info and this node became
         # standalone, even though this should not happen with the
         # new staged way of changing disk configs
         try:
       if stats.is_standalone:
         # peer had different config info and this node became
         # standalone, even though this should not happen with the
         # new staged way of changing disk configs
         try:
-          rd.ReAttachNet(multimaster)
+          rd.AttachNet(multimaster)
         except errors.BlockDeviceError, err:
           _Fail("Can't change network configuration: %s", err)
         except errors.BlockDeviceError, err:
           _Fail("Can't change network configuration: %s", err)
-    if all_connected:
-      break
-    time.sleep(sleep_time)
-    sleep_time = min(5, sleep_time * 1.5)
-  if not all_connected:
+
+    if not all_connected:
+      raise utils.RetryAgain()
+
+  try:
+    # Start with a delay of 100 miliseconds and go up to 5 seconds
+    utils.Retry(_Attach, (0.1, 1.5, 5.0), 2 * 60)
+  except utils.RetryTimeout:
     _Fail("Timeout in disk reconnecting")
     _Fail("Timeout in disk reconnecting")
+
   if multimaster:
     # change to primary mode
     for rd in bdevs:
   if multimaster:
     # change to primary mode
     for rd in bdevs:
@@ -2424,31 +2593,36 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
         rd.Open()
       except errors.BlockDeviceError, err:
         _Fail("Can't change to primary mode: %s", err)
         rd.Open()
       except errors.BlockDeviceError, err:
         _Fail("Can't change to primary mode: %s", err)
-  if multimaster:
-    msg = "multi-master and primary"
-  else:
-    msg = "single-master"
-  return (True, "Disks are now configured as %s" % msg)
 
 
 def DrbdWaitSync(nodes_ip, disks):
   """Wait until DRBDs have synchronized.
 
   """
 
 
 def DrbdWaitSync(nodes_ip, disks):
   """Wait until DRBDs have synchronized.
 
   """
+  def _helper(rd):
+    stats = rd.GetProcStatus()
+    if not (stats.is_connected or stats.is_in_resync):
+      raise utils.RetryAgain()
+    return stats
+
   bdevs = _FindDisks(nodes_ip, disks)
 
   min_resync = 100
   alldone = True
   bdevs = _FindDisks(nodes_ip, disks)
 
   min_resync = 100
   alldone = True
-  failure = False
   for rd in bdevs:
   for rd in bdevs:
-    stats = rd.GetProcStatus()
-    if not (stats.is_connected or stats.is_in_resync):
-      _Fail("DRBD device %s is not in sync: stats=%s", rd, stats)
+    try:
+      # poll each second for 15 seconds
+      stats = utils.Retry(_helper, 1, 15, args=[rd])
+    except utils.RetryTimeout:
+      stats = rd.GetProcStatus()
+      # last check
+      if not (stats.is_connected or stats.is_in_resync):
+        _Fail("DRBD device %s is not in sync: stats=%s", rd, stats)
     alldone = alldone and (not stats.is_in_resync)
     if stats.sync_percent is not None:
       min_resync = min(min_resync, stats.sync_percent)
 
     alldone = alldone and (not stats.is_in_resync)
     if stats.sync_percent is not None:
       min_resync = min(min_resync, stats.sync_percent)
 
-  return (True, (alldone, min_resync))
+  return (alldone, min_resync)
 
 
 def PowercycleNode(hypervisor_type):
 
 
 def PowercycleNode(hypervisor_type):
@@ -2461,11 +2635,11 @@ def PowercycleNode(hypervisor_type):
   hyper = hypervisor.GetHypervisor(hypervisor_type)
   try:
     pid = os.fork()
   hyper = hypervisor.GetHypervisor(hypervisor_type)
   try:
     pid = os.fork()
-  except OSError, err:
+  except OSError:
     # if we can't fork, we'll pretend that we're in the child process
     pid = 0
   if pid > 0:
     # if we can't fork, we'll pretend that we're in the child process
     pid = 0
   if pid > 0:
-    return (True, "Reboot scheduled in 5 seconds")
+    return "Reboot scheduled in 5 seconds"
   time.sleep(5)
   hyper.PowercycleNode()
 
   time.sleep(5)
   hyper.PowercycleNode()
 
@@ -2477,8 +2651,6 @@ class HooksRunner(object):
   on the master side.
 
   """
   on the master side.
 
   """
-  RE_MASK = re.compile("^[a-zA-Z0-9_-]+$")
-
   def __init__(self, hooks_base_dir=None):
     """Constructor for hooks runner.
 
   def __init__(self, hooks_base_dir=None):
     """Constructor for hooks runner.
 
@@ -2489,57 +2661,9 @@ class HooksRunner(object):
     """
     if hooks_base_dir is None:
       hooks_base_dir = constants.HOOKS_BASE_DIR
     """
     if hooks_base_dir is None:
       hooks_base_dir = constants.HOOKS_BASE_DIR
-    self._BASE_DIR = hooks_base_dir
-
-  @staticmethod
-  def ExecHook(script, env):
-    """Exec one hook script.
-
-    @type script: str
-    @param script: the full path to the script
-    @type env: dict
-    @param env: the environment with which to exec the script
-    @rtype: tuple (success, message)
-    @return: a tuple of success and message, where success
-        indicates the succes of the operation, and message
-        which will contain the error details in case we
-        failed
-
-    """
-    # exec the process using subprocess and log the output
-    fdstdin = None
-    try:
-      fdstdin = open("/dev/null", "r")
-      child = subprocess.Popen([script], stdin=fdstdin, stdout=subprocess.PIPE,
-                               stderr=subprocess.STDOUT, close_fds=True,
-                               shell=False, cwd="/", env=env)
-      output = ""
-      try:
-        output = child.stdout.read(4096)
-        child.stdout.close()
-      except EnvironmentError, err:
-        output += "Hook script error: %s" % str(err)
-
-      while True:
-        try:
-          result = child.wait()
-          break
-        except EnvironmentError, err:
-          if err.errno == errno.EINTR:
-            continue
-          raise
-    finally:
-      # try not to leak fds
-      for fd in (fdstdin, ):
-        if fd is not None:
-          try:
-            fd.close()
-          except EnvironmentError, err:
-            # just log the error
-            #logging.exception("Error while closing fd %s", fd)
-            pass
-
-    return result == 0, utils.SafeEncode(output.strip())
+    # yeah, _BASE_DIR is not valid for attributes, we use it like a
+    # constant
+    self._BASE_DIR = hooks_base_dir # pylint: disable-msg=C0103
 
   def RunHooks(self, hpath, phase, env):
     """Run the scripts in the hooks directory.
 
   def RunHooks(self, hpath, phase, env):
     """Run the scripts in the hooks directory.
@@ -2570,34 +2694,35 @@ class HooksRunner(object):
     else:
       _Fail("Unknown hooks phase '%s'", phase)
 
     else:
       _Fail("Unknown hooks phase '%s'", phase)
 
-    rr = []
 
     subdir = "%s-%s.d" % (hpath, suffix)
 
     subdir = "%s-%s.d" % (hpath, suffix)
-    dir_name = "%s/%s" % (self._BASE_DIR, subdir)
-    try:
-      dir_contents = utils.ListVisibleFiles(dir_name)
-    except OSError, err:
-      # FIXME: must log output in case of failures
-      return True, rr
-
-    # we use the standard python sort order,
-    # so 00name is the recommended naming scheme
-    dir_contents.sort()
-    for relname in dir_contents:
-      fname = os.path.join(dir_name, relname)
-      if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
-          self.RE_MASK.match(relname) is not None):
+    dir_name = utils.PathJoin(self._BASE_DIR, subdir)
+
+    results = []
+
+    if not os.path.isdir(dir_name):
+      # for non-existing/non-dirs, we simply exit instead of logging a
+      # warning at every operation
+      return results
+
+    runparts_results = utils.RunParts(dir_name, env=env, reset_env=True)
+
+    for (relname, relstatus, runresult)  in runparts_results:
+      if relstatus == constants.RUNPARTS_SKIP:
         rrval = constants.HKR_SKIP
         output = ""
         rrval = constants.HKR_SKIP
         output = ""
-      else:
-        result, output = self.ExecHook(fname, env)
-        if not result:
+      elif relstatus == constants.RUNPARTS_ERR:
+        rrval = constants.HKR_FAIL
+        output = "Hook script execution error: %s" % runresult
+      elif relstatus == constants.RUNPARTS_RUN:
+        if runresult.failed:
           rrval = constants.HKR_FAIL
         else:
           rrval = constants.HKR_SUCCESS
           rrval = constants.HKR_FAIL
         else:
           rrval = constants.HKR_SUCCESS
-      rr.append(("%s/%s" % (subdir, relname), rrval, output))
+        output = utils.SafeEncode(runresult.output.strip())
+      results.append(("%s/%s" % (subdir, relname), rrval, output))
 
 
-    return True, rr
+    return results
 
 
 class IAllocatorRunner(object):
 
 
 class IAllocatorRunner(object):
@@ -2607,7 +2732,8 @@ class IAllocatorRunner(object):
   the master side.
 
   """
   the master side.
 
   """
-  def Run(self, name, idata):
+  @staticmethod
+  def Run(name, idata):
     """Run an iallocator script.
 
     @type name: str
     """Run an iallocator script.
 
     @type name: str
@@ -2637,7 +2763,7 @@ class IAllocatorRunner(object):
     finally:
       os.unlink(fin_name)
 
     finally:
       os.unlink(fin_name)
 
-    return True, result.stdout
+    return result.stdout
 
 
 class DevCacheManager(object):
 
 
 class DevCacheManager(object):
@@ -2663,7 +2789,7 @@ class DevCacheManager(object):
     if dev_path.startswith(cls._DEV_PREFIX):
       dev_path = dev_path[len(cls._DEV_PREFIX):]
     dev_path = dev_path.replace("/", "_")
     if dev_path.startswith(cls._DEV_PREFIX):
       dev_path = dev_path[len(cls._DEV_PREFIX):]
     dev_path = dev_path.replace("/", "_")
-    fpath = "%s/bdev_%s" % (cls._ROOT_DIR, dev_path)
+    fpath = utils.PathJoin(cls._ROOT_DIR, "bdev_%s" % dev_path)
     return fpath
 
   @classmethod
     return fpath
 
   @classmethod
@@ -2698,7 +2824,7 @@ class DevCacheManager(object):
     try:
       utils.WriteFile(fpath, data=fdata)
     except EnvironmentError, err:
     try:
       utils.WriteFile(fpath, data=fdata)
     except EnvironmentError, err:
-      logging.exception("Can't update bdev cache for %s", dev_path)
+      logging.exception("Can't update bdev cache for %s: %s", dev_path, err)
 
   @classmethod
   def RemoveCache(cls, dev_path):
 
   @classmethod
   def RemoveCache(cls, dev_path):
@@ -2720,4 +2846,4 @@ class DevCacheManager(object):
     try:
       utils.RemoveFile(fpath)
     except EnvironmentError, err:
     try:
       utils.RemoveFile(fpath)
     except EnvironmentError, err:
-      logging.exception("Can't update bdev cache for %s", dev_path)
+      logging.exception("Can't update bdev cache for %s: %s", dev_path, err)