locking: Convert pipe condition to new timeout class
[ganeti-local] / lib / backend.py
index ab074da..4e40cb7 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
+
+"""
 
 
 import os
 
 
 import os
@@ -46,6 +51,9 @@ from ganeti import objects
 from ganeti import ssconf
 
 
 from ganeti import ssconf
 
 
+_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
+
+
 class RPCFail(Exception):
   """Class denoting RPC failure.
 
 class RPCFail(Exception):
   """Class denoting RPC failure.
 
@@ -53,6 +61,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.
 
@@ -68,10 +77,11 @@ def _Fail(msg, *args, **kwargs):
   """
   if args:
     msg = msg % args
   """
   if args:
     msg = msg % args
-  if "exc" in kwargs and kwargs["exc"]:
-    logging.exception(msg)
-  else:
-    logging.error(msg)
+  if "log" not in kwargs or kwargs["log"]: # if we should log this error
+    if "exc" in kwargs and kwargs["exc"]:
+      logging.exception(msg)
+    else:
+      logging.error(msg)
   raise RPCFail(msg)
 
 
   raise RPCFail(msg)
 
 
@@ -144,10 +154,37 @@ def _CleanDirectory(path, exclude=None):
       utils.RemoveFile(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.
 
-  @rtype: None
+  @rtype: tuple
+  @return: True, None
 
   """
   _CleanDirectory(constants.QUEUE_DIR, exclude=[constants.JOB_QUEUE_LOCK_FILE])
 
   """
   _CleanDirectory(constants.QUEUE_DIR, exclude=[constants.JOB_QUEUE_LOCK_FILE])
@@ -161,8 +198,8 @@ def GetMasterInfo():
   for consumption here or from the node daemon.
 
   @rtype: tuple
   for consumption here or from the node daemon.
 
   @rtype: tuple
-  @return: (master_netdev, master_ip, master_name) if we have a good
-      configuration, otherwise (None, None, None)
+  @return: master_netdev, master_ip, master_name
+  @raise RPCFail: in case of errors
 
   """
   try:
 
   """
   try:
@@ -171,12 +208,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:
-    logging.exception("Cluster configuration incomplete")
-    return (None, None, None)
+    _Fail("Cluster configuration incomplete: %s", err, exc=True)
   return (master_netdev, master_ip, master_node)
 
 
   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
@@ -184,30 +220,34 @@ 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
 
   """
   @rtype: None
 
   """
-  ok = True
+  # GetMasterInfo will raise an exception if not able to return data
   master_netdev, master_ip, _ = GetMasterInfo()
   master_netdev, master_ip, _ = GetMasterInfo()
-  if not master_netdev:
-    return False
 
 
+  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:
-      logging.debug("Already started")
+      logging.debug("Master IP already configured, doing nothing")
     else:
     else:
-      logging.error("Someone else has the master ip, not activating")
-      ok = False
+      msg = "Someone else has the master ip, not activating"
+      logging.error(msg)
+      err_msgs.append(msg)
   else:
     result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip,
                            "dev", master_netdev, "label",
                            "%s:0" % master_netdev])
     if result.failed:
   else:
     result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip,
                            "dev", master_netdev, "label",
                            "%s:0" % master_netdev])
     if result.failed:
-      logging.error("Can't activate master IP: %s", result.output)
-      ok = False
+      msg = "Can't activate master IP: %s" % result.output
+      logging.error(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])
@@ -215,12 +255,24 @@ 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])
+    daemons_params = {
+        'ganeti-masterd': [],
+        'ganeti-rapi': [],
+        }
+    if no_voting:
+      daemons_params['ganeti-masterd'].append('--no-voting')
+      daemons_params['ganeti-masterd'].append('--yes-do-it')
+    for daemon in daemons_params:
+      cmd = [daemon]
+      cmd.extend(daemons_params[daemon])
+      result = utils.RunCmd(cmd)
       if result.failed:
       if result.failed:
-        logging.error("Can't start daemon %s: %s", daemon, result.output)
-        ok = False
-  return ok
+        msg = "Can't start daemon %s: %s" % (daemon, result.output)
+        logging.error(msg)
+        err_msgs.append(msg)
+
+  if err_msgs:
+    _Fail("; ".join(err_msgs))
 
 
 def StopMaster(stop_daemons):
 
 
 def StopMaster(stop_daemons):
@@ -236,9 +288,11 @@ def StopMaster(stop_daemons):
   @rtype: None
 
   """
   @rtype: None
 
   """
+  # TODO: log and report back to the caller the error failures; we
+  # 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()
   master_netdev, master_ip, _ = GetMasterInfo()
-  if not master_netdev:
-    return False
 
   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])
@@ -248,11 +302,9 @@ def StopMaster(stop_daemons):
 
   if stop_daemons:
     # stop/kill the rapi and the master daemon
 
   if stop_daemons:
     # stop/kill the rapi and the master daemon
-    for daemon in constants.RAPI_PID, constants.MASTERD_PID:
+    for daemon in constants.RAPI, constants.MASTERD:
       utils.KillProcess(utils.ReadPidFile(utils.DaemonPidFileName(daemon)))
 
       utils.KillProcess(utils.ReadPidFile(utils.DaemonPidFileName(daemon)))
 
-  return True
-
 
 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
   """Joins this node to the cluster.
 
 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
   """Joins this node to the cluster.
@@ -298,8 +350,6 @@ def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
 
   utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
 
 
   utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
 
-  return (True, "Node added successfully")
-
 
 def LeaveCluster():
   """Cleans up and remove the current node.
 
 def LeaveCluster():
   """Cleans up and remove the current node.
@@ -317,25 +367,32 @@ def LeaveCluster():
 
   try:
     priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
 
   try:
     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")
   except errors.OpExecError:
     logging.exception("Error while processing ssh files")
-    return
 
 
-  f = open(pub_key, 'r')
   try:
   try:
-    utils.RemoveAuthorizedKey(auth_keys, f.read(8192))
-  finally:
-    f.close()
+    utils.RemoveFile(constants.HMAC_CLUSTER_KEY)
+    utils.RemoveFile(constants.RAPI_CERT_FILE)
+    utils.RemoveFile(constants.SSL_CERT_FILE)
+  except:
+    logging.exception("Error while removing cluster secrets")
 
 
-  utils.RemoveFile(priv_key)
-  utils.RemoveFile(pub_key)
+  confd_pid = utils.ReadPidFile(utils.DaemonPidFileName(constants.CONFD))
 
 
-  # Return a reassuring string to the caller, and quit
-  raise errors.QuitGanetiException(False, 'Shutdown scheduled')
+  if confd_pid:
+    utils.KillProcess(confd_pid, timeout=2)
+
+  # Raise a custom exception (handled in ganeti-noded)
+  raise errors.QuitGanetiException(True, 'Shutdown scheduled')
 
 
 def GetNodeInfo(vgname, hypervisor_type):
 
 
 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
@@ -361,11 +418,7 @@ 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 outputarray
 
 
   return outputarray
 
@@ -431,7 +484,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):
@@ -451,7 +504,7 @@ def VerifyNode(what, cluster_name):
       what[constants.NV_INSTANCELIST])
 
   if constants.NV_VGLIST in what:
       what[constants.NV_INSTANCELIST])
 
   if constants.NV_VGLIST in what:
-    result[constants.NV_VGLIST] = ListVolumeGroups()
+    result[constants.NV_VGLIST] = utils.ListVolumeGroups()
 
   if constants.NV_VERSION in what:
     result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
 
   if constants.NV_VERSION in what:
     result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
@@ -494,9 +547,7 @@ def GetVolumeList(vg_name):
                          "--separator=%s" % sep,
                          "-olv_name,lv_size,lv_attr", vg_name])
   if result.failed:
                          "--separator=%s" % sep,
                          "-olv_name,lv_size,lv_attr", vg_name])
   if result.failed:
-    logging.error("Failed to list logical volumes, lvs output: %s",
-                  result.output)
-    return result.output
+    _Fail("Failed to list logical volumes, lvs output: %s", result.output)
 
   valid_line_re = re.compile("^ *([^|]+)\|([0-9.]+)\|([^|]{6})\|?$")
   for line in result.stdout.splitlines():
 
   valid_line_re = re.compile("^ *([^|]+)\|([0-9.]+)\|([^|]{6})\|?$")
   for line in result.stdout.splitlines():
@@ -508,6 +559,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
@@ -547,9 +603,8 @@ def NodeVolumes():
                          "--separator=|",
                          "--options=lv_name,lv_size,devices,vg_name"])
   if result.failed:
                          "--separator=|",
                          "--options=lv_name,lv_size,devices,vg_name"])
   if result.failed:
-    logging.error("Failed to list logical volumes, lvs output: %s",
-                  result.output)
-    return []
+    _Fail("Failed to list logical volumes, lvs output: %s",
+          result.output)
 
   def parse_dev(dev):
     if '(' in dev:
 
   def parse_dev(dev):
     if '(' in dev:
@@ -576,11 +631,13 @@ def BridgesExist(bridges_list):
   @return: C{True} if all of them exist, C{False} otherwise
 
   """
   @return: C{True} if all of them exist, C{False} otherwise
 
   """
+  missing = []
   for bridge in bridges_list:
     if not utils.BridgeExists(bridge):
   for bridge in bridges_list:
     if not utils.BridgeExists(bridge):
-      return False
+      missing.append(bridge)
 
 
-  return True
+  if missing:
+    _Fail("Missing bridges %s", ", ".join(missing))
 
 
 def GetInstanceList(hypervisor_list):
 
 
 def GetInstanceList(hypervisor_list):
@@ -601,14 +658,14 @@ def GetInstanceList(hypervisor_list):
       names = hypervisor.GetHypervisor(hname).ListInstances()
       results.extend(names)
     except errors.HypervisorError, err:
       names = hypervisor.GetHypervisor(hname).ListInstances()
       results.extend(names)
     except errors.HypervisorError, err:
-      logging.exception("Error enumerating instances for hypevisor %s", hname)
-      raise
+      _Fail("Error enumerating instances (hypervisor %s): %s",
+            hname, err, exc=True)
 
   return results
 
 
 def GetInstanceInfo(instance, hname):
 
   return results
 
 
 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
@@ -646,15 +703,14 @@ def GetInstanceMigratable(instance):
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
-  if instance.name not in hyper.ListInstances():
-    return (False, 'not running')
+  iname = instance.name
+  if iname not in hyper.ListInstances():
+    _Fail("Instance %s is not running", iname)
 
   for idx in range(len(instance.disks)):
 
   for idx in range(len(instance.disks)):
-    link_name = _GetBlockDevSymlinkPath(instance.name, idx)
+    link_name = _GetBlockDevSymlinkPath(iname, idx)
     if not os.path.islink(link_name):
     if not os.path.islink(link_name):
-      return (False, 'not restarted since ganeti 1.2.5')
-
-  return (True, '')
+      _Fail("Instance %s was not restarted since ganeti 1.2.5", iname)
 
 
 def GetAllInstancesInfo(hypervisor_list):
 
 
 def GetAllInstancesInfo(hypervisor_list):
@@ -680,7 +736,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,
@@ -693,8 +749,8 @@ def GetAllInstancesInfo(hypervisor_list):
           # invocations of the different hypervisors
           for key in 'memory', 'vcpus':
             if value[key] != output[name][key]:
           # invocations of the different hypervisors
           for key in 'memory', 'vcpus':
             if value[key] != output[name][key]:
-              raise errors.HypervisorError("Instance %s is running twice"
-                                           " with different parameters" % name)
+              _Fail("Instance %s is running twice"
+                    " with different parameters", name)
         output[name] = value
 
   return output
         output[name] = value
 
   return output
@@ -707,21 +763,12 @@ def InstanceOsAdd(instance, reinstall):
   @param instance: Instance whose OS is to be installed
   @type reinstall: boolean
   @param reinstall: whether this is an instance reinstall
   @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
+  @rtype: None
 
   """
 
   """
-  try:
-    inst_os = OSFromDisk(instance.os)
-  except errors.InvalidOS, err:
-    os_name, os_dir, os_err = err.args
-    if os_dir is None:
-      return (False, "Can't find OS '%s': %s" % (os_name, os_err))
-    else:
-      return (False, "Error parsing OS '%s' in directory %s: %s" %
-              (os_name, os_dir, os_err))
+  inst_os = OSFromDisk(instance.os)
 
 
-  create_env = OSEnvironment(instance)
+  create_env = OSEnvironment(instance, inst_os)
   if reinstall:
     create_env['INSTANCE_REINSTALL'] = "1"
 
   if reinstall:
     create_env['INSTANCE_REINSTALL'] = "1"
 
@@ -736,10 +783,8 @@ def InstanceOsAdd(instance, reinstall):
                   result.output)
     lines = [utils.SafeEncode(val)
              for val in utils.TailFile(logfile, lines=20)]
                   result.output)
     lines = [utils.SafeEncode(val)
              for val in utils.TailFile(logfile, lines=20)]
-    return (False, "OS create script failed (%s), last lines in the"
-            " log file:\n%s" % (result.fail_reason, "\n".join(lines)))
-
-  return (True, "Successfully installed")
+    _Fail("OS create script failed (%s), last lines in the"
+          " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
 
 def RunRenameInstance(instance, old_name):
 
 
 def RunRenameInstance(instance, old_name):
@@ -755,7 +800,7 @@ def RunRenameInstance(instance, old_name):
   """
   inst_os = OSFromDisk(instance.os)
 
   """
   inst_os = OSFromDisk(instance.os)
 
-  rename_env = OSEnvironment(instance)
+  rename_env = OSEnvironment(instance, inst_os)
   rename_env['OLD_INSTANCE_NAME'] = old_name
 
   logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
   rename_env['OLD_INSTANCE_NAME'] = old_name
 
   logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
@@ -770,14 +815,12 @@ def RunRenameInstance(instance, old_name):
                   result.cmd, result.fail_reason, result.output)
     lines = [utils.SafeEncode(val)
              for val in utils.TailFile(logfile, lines=20)]
                   result.cmd, result.fail_reason, result.output)
     lines = [utils.SafeEncode(val)
              for val in utils.TailFile(logfile, lines=20)]
-    return (False, "OS rename script failed (%s), last lines in the"
-            " log file:\n%s" % (result.fail_reason, "\n".join(lines)))
-
-  return (True, "Rename successful")
+    _Fail("OS rename script failed (%s), last lines in the"
+          " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
 
 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
@@ -809,7 +852,7 @@ def _GetVGInfo(vg_name):
         "pv_count": int(valarr[2]),
         }
     except ValueError, err:
         "pv_count": int(valarr[2]),
         }
     except ValueError, err:
-      logging.exception("Fail to parse vgs output")
+      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))
@@ -853,7 +896,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:
@@ -897,14 +940,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)
@@ -916,59 +959,63 @@ 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])
-
-  if instance.name not in running_instances:
-    return (True, "Instance already stopped")
-
   hyper = hypervisor.GetHypervisor(hv_name)
   hyper = hypervisor.GetHypervisor(hv_name)
-  try:
-    hyper.StopInstance(instance)
-  except errors.HypervisorError, err:
-    _Fail("Failed to stop instance %s: %s", instance.name, err)
+  running_instances = hyper.ListInstances()
+  iname = instance.name
+
+  if iname not in running_instances:
+    logging.info("Instance %s not running, doing nothing", iname)
+    return
 
 
-  # test every 10secs for 2min
+  start = time.time()
+  end = start + timeout
+  sleep_time = 1
 
 
-  time.sleep(1)
-  for dummy in range(11):
-    if instance.name not in GetInstanceList([hv_name]):
+  tried_once = False
+  while not tried_once and time.time() < end:
+    try:
+      hyper.StopInstance(instance, retry=tried_once)
+    except errors.HypervisorError, err:
+      _Fail("Failed to stop instance %s: %s", iname, err)
+    tried_once = True
+    time.sleep(sleep_time)
+    if instance.name not in hyper.ListInstances():
       break
       break
-    time.sleep(10)
+    if sleep_time < 5:
+      # 1.2 behaves particularly good for our case:
+      # it gives us 10 increasing steps and caps just slightly above 5 seconds
+      sleep_time *= 1.2
   else:
     # the shutdown did not succeed
   else:
     # 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)
+      _Fail("Failed to force stop instance %s: %s", iname, err)
 
     time.sleep(1)
     if instance.name in GetInstanceList([hv_name]):
 
     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)
+      _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}
@@ -980,11 +1027,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 timeout: integer
+  @param timeout: maximum timeout for soft shutdown
+  @rtype: None
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
 
   """
   running_instances = GetInstanceList([instance.hypervisor])
@@ -1000,17 +1049,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.
@@ -1024,7 +1069,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):
@@ -1043,7 +1088,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):
@@ -1062,7 +1106,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):
@@ -1087,7 +1130,6 @@ def MigrateInstance(instance, target, live):
     hyper.MigrateInstance(instance.name, target, live)
   except errors.HypervisorError, err:
     _Fail("Failed to migrate instance: %s", err, exc=True)
     hyper.MigrateInstance(instance.name, target, live)
   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):
@@ -1128,7 +1170,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
       clist.append(crdev)
 
   try:
       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)
 
@@ -1148,8 +1190,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):
@@ -1164,7 +1205,6 @@ def BlockdevRemove(disk):
 
   """
   msgs = []
 
   """
   msgs = []
-  result = True
   try:
     rdev = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
   try:
     rdev = _RecursiveFindBD(disk)
   except errors.BlockDeviceError, err:
@@ -1177,18 +1217,18 @@ 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))
 
 
-  return (result, "; ".join(msgs))
+  if msgs:
+    _Fail("; ".join(msgs))
 
 
 def _RecursiveAssembleBD(disk, owner, as_primary):
 
 
 def _RecursiveAssembleBD(disk, owner, as_primary):
@@ -1232,7 +1272,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():
@@ -1255,22 +1295,20 @@ def BlockdevAssemble(disk, owner, as_primary):
       C{True} for secondary nodes
 
   """
       C{True} for secondary nodes
 
   """
-  status = True
-  result = "no error information"
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
       result = result.dev_path
   except errors.BlockDeviceError, err:
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
     if isinstance(result, bdev.BlockDev):
       result = result.dev_path
   except errors.BlockDeviceError, err:
-    result = "Error while assembling disk: %s" % str(err)
-    status = False
-  return (status, result)
+    _Fail("Error while assembling disk: %s", err, exc=True)
+
+  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.
 
@@ -1281,12 +1319,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
@@ -1295,16 +1331,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))
 
 
-  return (result, "; ".join(msgs))
+  if msgs:
+    _Fail("; ".join(msgs))
 
 
 def BlockdevAddchildren(parent_cdev, new_cdevs):
 
 
 def BlockdevAddchildren(parent_cdev, new_cdevs):
@@ -1314,8 +1350,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)
@@ -1325,7 +1360,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):
@@ -1335,8 +1369,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)
@@ -1354,7 +1387,6 @@ def BlockdevRemovechildren(parent_cdev, new_cdevs):
     else:
       devs.append(rpath)
   parent_bdev.RemoveChildren(devs)
     else:
       devs.append(rpath)
   parent_bdev.RemoveChildren(devs)
-  return (True, None)
 
 
 def BlockdevGetmirrorstatus(disks):
 
 
 def BlockdevGetmirrorstatus(disks):
@@ -1375,14 +1407,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
@@ -1396,29 +1430,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, err:
+      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):
@@ -1441,28 +1550,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)
 
@@ -1470,7 +1564,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):
@@ -1500,7 +1593,7 @@ def _ErrnoOrStr(err):
   return detail
 
 
   return detail
 
 
-def _OSOndiskVersion(name, os_dir):
+def _OSOndiskAPIVersion(name, os_dir):
   """Compute and return the API version of a given OS.
 
   This function will try to read the API version of the OS given by
   """Compute and return the API version of a given OS.
 
   This function will try to read the API version of the OS given by
@@ -1510,43 +1603,36 @@ def _OSOndiskVersion(name, os_dir):
   @param name: the OS name we should look for
   @type os_dir: str
   @param os_dir: the directory inwhich we should look for the OS
   @param name: the OS name we should look for
   @type os_dir: str
   @param os_dir: the directory inwhich we should look for the OS
-  @rtype: int or None
-  @return:
-      Either an integer denoting the version or None in the
-      case when this is not a valid OS name.
-  @raise errors.InvalidOS: if the OS cannot be found
+  @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 = os.path.sep.join([os_dir, constants.OS_API_FILE])
 
   try:
     st = os.stat(api_file)
   except EnvironmentError, err:
 
   try:
     st = os.stat(api_file)
   except EnvironmentError, err:
-    raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file not"
-                           " found (%s)" % _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)):
-    raise errors.InvalidOS(name, os_dir, "'ganeti_api_version' file is not"
-                           " a regular file")
+    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:
   except EnvironmentError, err:
-    raise errors.InvalidOS(name, os_dir, "error while reading the"
-                           " API version (%s)" % _ErrnoOrStr(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:
   except (TypeError, ValueError), err:
-    raise errors.InvalidOS(name, os_dir,
-                           "API version is not integer (%s)" % str(err))
+    return False, ("API version(s) can't be converted to integer: %s" %
+                   str(err))
 
 
-  return api_versions
+  return True, api_versions
 
 
 def DiagnoseOS(top_dirs=None):
 
 
 def DiagnoseOS(top_dirs=None):
@@ -1557,8 +1643,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: an OS object for each name in all the given
-      directories
+  @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
+          - variants is a list of supported OS variants, if any
 
   """
   if top_dirs is None:
 
   """
   if top_dirs is None:
@@ -1570,80 +1661,131 @@ 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:
-        try:
-          os_inst = OSFromDisk(name, base_dir=dir_name)
-          result.append(os_inst)
-        except errors.InvalidOS, err:
-          result.append(objects.OS.FromInvalidOS(err))
+        os_path = os.path.sep.join([dir_name, name])
+        status, os_inst = _TryOSFromDisk(name, base_dir=dir_name)
+        if status:
+          diagnose = ""
+          variants = os_inst.supported_variants
+        else:
+          diagnose = os_inst
+          variants = []
+        result.append((name, os_path, status, diagnose, variants))
 
   return result
 
 
 
   return result
 
 
-def OSFromDisk(name, base_dir=None):
+def _TryOSFromDisk(name, base_dir=None):
   """Create an OS instance from disk.
 
   This function will return an OS instance if the given name is a
   """Create an OS instance from disk.
 
   This function will return an OS instance if the given name is a
-  valid OS name. Otherwise, it will raise an appropriate
-  L{errors.InvalidOS} exception, detailing why this is not a valid OS.
+  valid OS name.
 
   @type base_dir: string
   @keyword base_dir: Base directory containing OS installations.
                      Defaults to a search in all the OS_SEARCH_PATH dirs.
 
   @type base_dir: string
   @keyword base_dir: Base directory containing OS installations.
                      Defaults to a search in all the OS_SEARCH_PATH dirs.
-  @rtype: L{objects.OS}
-  @return: the OS instance if we find a valid one
-  @raise errors.InvalidOS: if we don't find a valid OS
+  @rtype: tuple
+  @return: success and either the OS instance if we find a valid one,
+      or error message
 
   """
   if base_dir is None:
     os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
     if os_dir is None:
 
   """
   if base_dir is None:
     os_dir = utils.FindFile(name, constants.OS_SEARCH_PATH, os.path.isdir)
     if os_dir is None:
-      raise errors.InvalidOS(name, None, "OS dir not found in search path")
+      return False, "Directory for OS %s not found in search path" % name
   else:
     os_dir = os.path.sep.join([base_dir, name])
 
   else:
     os_dir = os.path.sep.join([base_dir, name])
 
-  api_versions = _OSOndiskVersion(name, os_dir)
+  status, api_versions = _OSOndiskAPIVersion(name, os_dir)
+  if not status:
+    # push the error up
+    return status, api_versions
+
+  if not constants.OS_API_VERSIONS.intersection(api_versions):
+    return False, ("API version mismatch for path '%s': found %s, want %s." %
+                   (os_dir, api_versions, constants.OS_API_VERSIONS))
 
 
-  if constants.OS_API_VERSION not in api_versions:
-    raise errors.InvalidOS(name, os_dir, "API version mismatch"
-                           " (found %s want %s)"
-                           % (api_versions, constants.OS_API_VERSION))
+  # 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 name in os_files:
+    os_files[name] = os.path.sep.join([os_dir, name])
 
     try:
 
     try:
-      st = os.stat(os_scripts[script])
+      st = os.stat(os_files[name])
     except EnvironmentError, err:
     except EnvironmentError, err:
-      raise errors.InvalidOS(name, os_dir, "'%s' script missing (%s)" %
-                             (script, _ErrnoOrStr(err)))
-
-    if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
-      raise errors.InvalidOS(name, os_dir, "'%s' script not executable" %
-                             script)
+      return False, ("File '%s' under path '%s' is missing (%s)" %
+                     (name, 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)):
-      raise errors.InvalidOS(name, os_dir, "'%s' is not a regular file" %
-                             script)
+      return False, ("File '%s' under path '%s' is not a regular file" %
+                     (name, os_dir))
+
+    if name 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" %
+                       (name, 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")
 
 
-  return objects.OS(name=name, path=os_dir, status=constants.OS_VALID_STATUS,
-                    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],
-                    api_versions=api_versions)
+  os_obj = objects.OS(name=name, path=os_dir,
+                      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
+
+
+def OSFromDisk(name, base_dir=None):
+  """Create an OS instance from disk.
 
 
-def OSEnvironment(instance, debug=0):
+  This function will return an OS instance if the given name is a
+  valid OS name. Otherwise, it will raise an appropriate
+  L{RPCFail} exception, detailing why this is not a valid OS.
+
+  This is just a wrapper over L{_TryOSFromDisk}, which doesn't raise
+  an exception but returns true/false status data.
+
+  @type base_dir: string
+  @keyword base_dir: Base directory containing OS installations.
+                     Defaults to a search in all the OS_SEARCH_PATH dirs.
+  @rtype: L{objects.OS}
+  @return: the OS instance if we find a valid one
+  @raise RPCFail: if we don't find a valid OS
+
+  """
+  name_only = name.split("+", 1)[0]
+  status, payload = _TryOSFromDisk(name_only, base_dir)
+
+  if not status:
+    _Fail(payload)
+
+  return payload
+
+
+def OSEnvironment(instance, 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 os: L{objects.OS}
+  @param 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
@@ -1653,13 +1795,20 @@ def OSEnvironment(instance, debug=0):
 
   """
   result = {}
 
   """
   result = {}
-  result['OS_API_VERSION'] = '%d' % constants.OS_API_VERSION
+  api_version = max(constants.OS_API_VERSIONS.intersection(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 = 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:
@@ -1689,6 +1838,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):
@@ -1707,15 +1860,13 @@ def BlockdevGrow(disk, amount):
   """
   r_dev = _RecursiveFindBD(disk)
   if r_dev is None:
   """
   r_dev = _RecursiveFindBD(disk)
   if r_dev is None:
-    return False, "Cannot find block device %s" % (disk,)
+    _Fail("Cannot find block device %s", disk)
 
   try:
     r_dev.Grow(amount)
   except errors.BlockDeviceError, err:
     _Fail("Failed to grow block device: %s", err, exc=True)
 
 
   try:
     r_dev.Grow(amount)
   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.
@@ -1745,11 +1896,10 @@ def BlockdevSnapshot(disk):
       # let's stay on the safe side and ask for the full size, for now
       return r_dev.Snapshot(disk.size)
     else:
       # let's stay on the safe side and ask for the full size, for now
       return r_dev.Snapshot(disk.size)
     else:
-      return None
+      _Fail("Cannot find block device %s", disk)
   else:
   else:
-    raise errors.ProgrammerError("Cannot snapshot non-lvm block device"
-                                 " '%s' of type '%s'" %
-                                 (disk.unique_id, disk.dev_type))
+    _Fail("Cannot snapshot non-lvm block device '%s' of type '%s'",
+          disk.unique_id, disk.dev_type)
 
 
 def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
 
 
 def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
@@ -1766,13 +1916,12 @@ 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
+  @rtype: None
 
   """
 
   """
-  export_env = OSEnvironment(instance)
-
   inst_os = OSFromDisk(instance.os)
   inst_os = OSFromDisk(instance.os)
+  export_env = OSEnvironment(instance, inst_os)
+
   export_script = inst_os.export_script
 
   logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
   export_script = inst_os.export_script
 
   logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
@@ -1781,8 +1930,8 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
     os.mkdir(constants.LOG_OS_DIR, 0750)
   real_disk = _RecursiveFindBD(disk)
   if real_disk is None:
     os.mkdir(constants.LOG_OS_DIR, 0750)
   real_disk = _RecursiveFindBD(disk)
   if real_disk is None:
-    raise errors.BlockDeviceError("Block device '%s' is not set up" %
-                                  str(disk))
+    _Fail("Block device '%s' is not set up", disk)
+
   real_disk.Open()
 
   export_env['EXPORT_DEVICE'] = real_disk.dev_path
   real_disk.Open()
 
   export_env['EXPORT_DEVICE'] = real_disk.dev_path
@@ -1794,8 +1943,8 @@ def ExportSnapshot(disk, dest_node, instance, cluster_name, idx):
   # the target command is built out of three individual commands,
   # which are joined by pipes; we check each individual command for
   # valid parameters
   # 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"
 
@@ -1808,14 +1957,11 @@ 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:
 
   if result.failed:
-    logging.error("os snapshot export command '%s' returned error: %s"
-                  " output: %s", command, result.fail_reason, result.output)
-    return False
-
-  return True
+    _Fail("OS snapshot export command '%s' returned error: %s"
+          " output: %s", command, result.fail_reason, result.output)
 
 
 def FinalizeExport(instance, snap_disks):
 
 
 def FinalizeExport(instance, snap_disks):
@@ -1828,8 +1974,7 @@ 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")
 
   """
   destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
@@ -1881,8 +2026,6 @@ def FinalizeExport(instance, snap_disks):
   shutil.rmtree(finaldestdir, True)
   shutil.move(destdir, finaldestdir)
 
   shutil.rmtree(finaldestdir, True)
   shutil.move(destdir, finaldestdir)
 
-  return True
-
 
 def ExportInfo(dest):
   """Get export configuration information.
 
 def ExportInfo(dest):
   """Get export configuration information.
@@ -1902,9 +2045,9 @@ def ExportInfo(dest):
 
   if (not config.has_section(constants.INISECT_EXP) or
       not config.has_section(constants.INISECT_INS)):
 
   if (not config.has_section(constants.INISECT_EXP) or
       not config.has_section(constants.INISECT_INS)):
-    return None
+    _Fail("Export info file doesn't have the required fields")
 
 
-  return config
+  return config.Dumps()
 
 
 def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
 
 
 def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
@@ -1920,8 +2063,8 @@ def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
   @return: each boolean represent the success of importing the n-th disk
 
   """
   @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)
   import_script = inst_os.import_script
 
   logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
   import_script = inst_os.import_script
 
   logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
@@ -1948,13 +2091,11 @@ def ImportOSIntoInstance(instance, src_node, src_images, cluster_name):
         logging.error("Disk import command '%s' returned error: %s"
                       " output: %s", command, result.fail_reason,
                       result.output)
         logging.error("Disk import command '%s' returned error: %s"
                       " output: %s", command, result.fail_reason,
                       result.output)
-        final_result.append(False)
-      else:
-        final_result.append(True)
-    else:
-      final_result.append(True)
+        final_result.append("error importing disk %d: %s, %s" %
+                            (idx, result.fail_reason, result.output[-100]))
 
 
-  return final_result
+  if final_result:
+    _Fail("; ".join(final_result), log=False)
 
 
 def ListExports():
 
 
 def ListExports():
@@ -1967,7 +2108,7 @@ def ListExports():
   if os.path.isdir(constants.EXPORT_DIR):
     return utils.ListVisibleFiles(constants.EXPORT_DIR)
   else:
   if os.path.isdir(constants.EXPORT_DIR):
     return utils.ListVisibleFiles(constants.EXPORT_DIR)
   else:
-    return []
+    _Fail("No exports directory")
 
 
 def RemoveExport(export):
 
 
 def RemoveExport(export):
@@ -1975,17 +2116,15 @@ 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 = os.path.join(constants.EXPORT_DIR, export)
 
-  shutil.rmtree(target)
-  # TODO: catch some of the relevant exceptions and provide a pretty
-  # error message if rmtree fails.
-
-  return True
+  try:
+    shutil.rmtree(target)
+  except EnvironmentError, err:
+    _Fail("Error while removing the export: %s", err, exc=True)
 
 
 def BlockdevRename(devlist):
 
 
 def BlockdevRename(devlist):
@@ -2025,7 +2164,8 @@ def BlockdevRename(devlist):
                   (dev, unique_id, err))
       logging.exception("Can't rename device '%s' to '%s'", dev, unique_id)
       result = False
                   (dev, unique_id, err))
       logging.exception("Can't rename device '%s' to '%s'", dev, unique_id)
       result = False
-  return (result, "; ".join(msgs))
+  if not result:
+    _Fail("; ".join(msgs))
 
 
 def _TransformFileStorageDir(file_storage_dir):
 
 
 def _TransformFileStorageDir(file_storage_dir):
@@ -2046,10 +2186,8 @@ def _TransformFileStorageDir(file_storage_dir):
   base_file_storage_dir = cfg.GetFileStorageDir()
   if (not os.path.commonprefix([file_storage_dir, base_file_storage_dir]) ==
       base_file_storage_dir):
   base_file_storage_dir = cfg.GetFileStorageDir()
   if (not os.path.commonprefix([file_storage_dir, base_file_storage_dir]) ==
       base_file_storage_dir):
-    logging.error("file storage directory '%s' is not under base file"
-                  " storage directory '%s'",
-                  file_storage_dir, base_file_storage_dir)
-    return None
+    _Fail("File storage directory '%s' is not under base file"
+          " storage directory '%s'", file_storage_dir, base_file_storage_dir)
   return file_storage_dir
 
 
   return file_storage_dir
 
 
@@ -2065,22 +2203,16 @@ def CreateFileStorageDir(file_storage_dir):
 
   """
   file_storage_dir = _TransformFileStorageDir(file_storage_dir)
 
   """
   file_storage_dir = _TransformFileStorageDir(file_storage_dir)
-  result = True,
-  if not file_storage_dir:
-    result = False,
+  if os.path.exists(file_storage_dir):
+    if not os.path.isdir(file_storage_dir):
+      _Fail("Specified storage dir '%s' is not a directory",
+            file_storage_dir)
   else:
   else:
-    if os.path.exists(file_storage_dir):
-      if not os.path.isdir(file_storage_dir):
-        logging.error("'%s' is not a directory", file_storage_dir)
-        result = False,
-    else:
-      try:
-        os.makedirs(file_storage_dir, 0750)
-      except OSError, err:
-        logging.error("Cannot create file storage directory '%s': %s",
-                      file_storage_dir, err)
-        result = False,
-  return result
+    try:
+      os.makedirs(file_storage_dir, 0750)
+    except OSError, err:
+      _Fail("Cannot create file storage directory '%s': %s",
+            file_storage_dir, err, exc=True)
 
 
 def RemoveFileStorageDir(file_storage_dir):
 
 
 def RemoveFileStorageDir(file_storage_dir):
@@ -2092,26 +2224,20 @@ 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)
-  result = True,
-  if not file_storage_dir:
-    result = False,
-  else:
-    if os.path.exists(file_storage_dir):
-      if not os.path.isdir(file_storage_dir):
-        logging.error("'%s' is not a directory", file_storage_dir)
-        result = False,
-      # deletes dir only if empty, otherwise we want to return False
-      try:
-        os.rmdir(file_storage_dir)
-      except OSError, err:
-        logging.exception("Cannot remove file storage directory '%s'",
-                          file_storage_dir)
-        result = False,
-  return result
+  if os.path.exists(file_storage_dir):
+    if not os.path.isdir(file_storage_dir):
+      _Fail("Specified Storage directory '%s' is not a directory",
+            file_storage_dir)
+    # deletes dir only if empty, otherwise we want to fail the rpc call
+    try:
+      os.rmdir(file_storage_dir)
+    except OSError, err:
+      _Fail("Cannot remove file storage directory '%s': %s",
+            file_storage_dir, err)
 
 
 def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
 
 
 def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
@@ -2128,46 +2254,37 @@ def RenameFileStorageDir(old_file_storage_dir, new_file_storage_dir):
   """
   old_file_storage_dir = _TransformFileStorageDir(old_file_storage_dir)
   new_file_storage_dir = _TransformFileStorageDir(new_file_storage_dir)
   """
   old_file_storage_dir = _TransformFileStorageDir(old_file_storage_dir)
   new_file_storage_dir = _TransformFileStorageDir(new_file_storage_dir)
-  result = True,
-  if not old_file_storage_dir or not new_file_storage_dir:
-    result = False,
-  else:
-    if not os.path.exists(new_file_storage_dir):
-      if os.path.isdir(old_file_storage_dir):
-        try:
-          os.rename(old_file_storage_dir, new_file_storage_dir)
-        except OSError, err:
-          logging.exception("Cannot rename '%s' to '%s'",
-                            old_file_storage_dir, new_file_storage_dir)
-          result =  False,
-      else:
-        logging.error("'%s' is not a directory", old_file_storage_dir)
-        result = False,
+  if not os.path.exists(new_file_storage_dir):
+    if os.path.isdir(old_file_storage_dir):
+      try:
+        os.rename(old_file_storage_dir, new_file_storage_dir)
+      except OSError, err:
+        _Fail("Cannot rename '%s' to '%s': %s",
+              old_file_storage_dir, new_file_storage_dir, err)
     else:
     else:
-      if os.path.exists(old_file_storage_dir):
-        logging.error("Cannot rename '%s' to '%s'. Both locations exist.",
-                      old_file_storage_dir, new_file_storage_dir)
-        result = False,
-  return result
+      _Fail("Specified storage dir '%s' is not a directory",
+            old_file_storage_dir)
+  else:
+    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)
 
 
 
 
-def _IsJobQueueFile(file_name):
+def _EnsureJobQueueFile(file_name):
   """Checks whether the given filename is in the queue directory.
 
   @type file_name: str
   @param file_name: the file name we should check
   """Checks whether the given filename is in the queue directory.
 
   @type file_name: str
   @param file_name: the file name we should check
-  @rtype: boolean
-  @return: whether the file is under the queue directory
+  @rtype: None
+  @raises RPCFail: if the file is not valid
 
   """
   queue_dir = os.path.normpath(constants.QUEUE_DIR)
   result = (os.path.commonprefix([queue_dir, file_name]) == queue_dir)
 
   if not result:
 
   """
   queue_dir = os.path.normpath(constants.QUEUE_DIR)
   result = (os.path.commonprefix([queue_dir, file_name]) == queue_dir)
 
   if not result:
-    logging.error("'%s' is not a file in the queue directory",
-                  file_name)
-
-  return result
+    _Fail("Passed job queue file '%s' does not belong to"
+          " the queue directory '%s'", file_name, queue_dir)
 
 
 def JobQueueUpdate(file_name, content):
 
 
 def JobQueueUpdate(file_name, content):
@@ -2184,14 +2301,11 @@ def JobQueueUpdate(file_name, content):
   @return: the success of the operation
 
   """
   @return: the success of the operation
 
   """
-  if not _IsJobQueueFile(file_name):
-    return False
+  _EnsureJobQueueFile(file_name)
 
   # 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
-
 
 def JobQueueRename(old, new):
   """Renames a job queue file.
 
 def JobQueueRename(old, new):
   """Renames a job queue file.
@@ -2202,17 +2316,15 @@ def JobQueueRename(old, new):
   @param old: the old (actual) file name
   @type new: str
   @param new: the desired file name
   @param old: the old (actual) file name
   @type new: str
   @param new: the desired file name
-  @rtype: boolean
-  @return: the success of the operation
+  @rtype: tuple
+  @return: the success of the operation and payload
 
   """
 
   """
-  if not (_IsJobQueueFile(old) and _IsJobQueueFile(new)):
-    return False
+  _EnsureJobQueueFile(old)
+  _EnsureJobQueueFile(new)
 
   utils.RenameFile(old, new, mkdir=True)
 
 
   utils.RenameFile(old, new, mkdir=True)
 
-  return True
-
 
 def JobQueueSetDrainFlag(drain_flag):
   """Set the drain flag for the queue.
 
 def JobQueueSetDrainFlag(drain_flag):
   """Set the drain flag for the queue.
@@ -2221,8 +2333,8 @@ def JobQueueSetDrainFlag(drain_flag):
 
   @type drain_flag: boolean
   @param drain_flag: if True, will set the drain flag, otherwise reset it.
 
   @type drain_flag: boolean
   @param drain_flag: if True, will set the drain flag, otherwise reset it.
-  @rtype: boolean
-  @return: always True
+  @rtype: truple
+  @return: always True, None
   @warning: the function always returns True
 
   """
   @warning: the function always returns True
 
   """
@@ -2231,8 +2343,6 @@ def JobQueueSetDrainFlag(drain_flag):
   else:
     utils.RemoveFile(constants.JOB_QUEUE_DRAIN_FILE)
 
   else:
     utils.RemoveFile(constants.JOB_QUEUE_DRAIN_FILE)
 
-  return True
-
 
 def BlockdevClose(instance_name, disks):
   """Closes the given block devices.
 
 def BlockdevClose(instance_name, disks):
   """Closes the given block devices.
@@ -2265,11 +2375,10 @@ def BlockdevClose(instance_name, disks):
     except errors.BlockDeviceError, err:
       msg.append(str(err))
   if msg:
     except errors.BlockDeviceError, err:
       msg.append(str(err))
   if msg:
-    return (False, "Can't make devices secondary: %s" % ",".join(msg))
+    _Fail("Can't make devices secondary: %s", ",".join(msg))
   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):
@@ -2279,19 +2388,14 @@ 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:
   except errors.HypervisorError, err:
-    return (False, str(err))
+    _Fail(str(err), log=False)
 
 
 def DemoteFromMC():
 
 
 def DemoteFromMC():
@@ -2301,17 +2405,17 @@ def DemoteFromMC():
   # try to ensure we're not the master by mistake
   master, myself = ssconf.GetMasterAndMyself()
   if master == myself:
   # try to ensure we're not the master by mistake
   master, myself = ssconf.GetMasterAndMyself()
   if master == myself:
-    return (False, "ssconf status shows I'm the master node, will not demote")
-  pid_file = utils.DaemonPidFileName(constants.MASTERD_PID)
+    _Fail("ssconf status shows I'm the master node, will not demote")
+  pid_file = utils.DaemonPidFileName(constants.MASTERD)
   if utils.IsProcessAlive(utils.ReadPidFile(pid_file)):
   if utils.IsProcessAlive(utils.ReadPidFile(pid_file)):
-    return (False, "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:
   except EnvironmentError, err:
     if err.errno != errno.ENOENT:
-      return (False, "Error while backing up cluster file: %s" % str(err))
+      _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):
@@ -2328,18 +2432,16 @@ def _FindDisks(nodes_ip, disks):
   for cf in disks:
     rd = _RecursiveFindBD(cf)
     if rd is None:
   for cf in disks:
     rd = _RecursiveFindBD(cf)
     if rd is None:
-      return (False, "Can't find device %s" % cf)
+      _Fail("Can't find device %s", cf)
     bdevs.append(rd)
     bdevs.append(rd)
-  return (True, bdevs)
+  return bdevs
 
 
 def DrbdDisconnectNet(nodes_ip, disks):
   """Disconnects the network on a list of drbd devices.
 
   """
 
 
 def DrbdDisconnectNet(nodes_ip, disks):
   """Disconnects the network on a list of drbd devices.
 
   """
-  status, bdevs = _FindDisks(nodes_ip, disks)
-  if not status:
-    return status, bdevs
+  bdevs = _FindDisks(nodes_ip, disks)
 
   # disconnect disks
   for rd in bdevs:
 
   # disconnect disks
   for rd in bdevs:
@@ -2348,16 +2450,13 @@ 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):
   """Attaches the network on a list of drbd devices.
 
   """
 
 
 def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
   """Attaches the network on a list of drbd devices.
 
   """
-  status, bdevs = _FindDisks(nodes_ip, disks)
-  if not status:
-    return status, bdevs
+  bdevs = _FindDisks(nodes_ip, disks)
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
@@ -2391,7 +2490,7 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
         # standalone, even though this should not happen with the
         # new staged way of changing disk configs
         try:
         # 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)
     if all_connected:
         except errors.BlockDeviceError, err:
           _Fail("Can't change network configuration: %s", err)
     if all_connected:
@@ -2399,7 +2498,7 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
     time.sleep(sleep_time)
     sleep_time = min(5, sleep_time * 1.5)
   if not all_connected:
     time.sleep(sleep_time)
     sleep_time = min(5, sleep_time * 1.5)
   if not all_connected:
-    return (False, "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:
@@ -2407,33 +2506,25 @@ 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.
 
   """
-  status, bdevs = _FindDisks(nodes_ip, disks)
-  if not status:
-    return status, bdevs
+  bdevs = _FindDisks(nodes_ip, disks)
 
   min_resync = 100
   alldone = True
 
   min_resync = 100
   alldone = True
-  failure = False
   for rd in bdevs:
     stats = rd.GetProcStatus()
     if not (stats.is_connected or stats.is_in_resync):
   for rd in bdevs:
     stats = rd.GetProcStatus()
     if not (stats.is_connected or stats.is_in_resync):
-      failure = True
-      break
+      _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 (not failure, (alldone, min_resync))
+
+  return (alldone, min_resync)
 
 
 def PowercycleNode(hypervisor_type):
 
 
 def PowercycleNode(hypervisor_type):
@@ -2446,11 +2537,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()
 
@@ -2553,14 +2644,15 @@ class HooksRunner(object):
     elif phase == constants.HOOKS_PHASE_POST:
       suffix = "post"
     else:
     elif phase == constants.HOOKS_PHASE_POST:
       suffix = "post"
     else:
-      raise errors.ProgrammerError("Unknown hooks phase: '%s'" % phase)
+      _Fail("Unknown hooks phase '%s'", phase)
+
     rr = []
 
     subdir = "%s-%s.d" % (hpath, suffix)
     dir_name = "%s/%s" % (self._BASE_DIR, subdir)
     try:
       dir_contents = utils.ListVisibleFiles(dir_name)
     rr = []
 
     subdir = "%s-%s.d" % (hpath, suffix)
     dir_name = "%s/%s" % (self._BASE_DIR, subdir)
     try:
       dir_contents = utils.ListVisibleFiles(dir_name)
-    except OSError, err:
+    except OSError:
       # FIXME: must log output in case of failures
       return rr
 
       # FIXME: must log output in case of failures
       return rr
 
@@ -2600,17 +2692,15 @@ class IAllocatorRunner(object):
     @param idata: the allocator input data
 
     @rtype: tuple
     @param idata: the allocator input data
 
     @rtype: tuple
-    @return: four element tuple of:
-       - run status (one of the IARUN_ constants)
-       - stdout
-       - stderr
-       - fail reason (as from L{utils.RunResult})
+    @return: two element tuple of:
+       - status
+       - either error message or stdout of allocator (for success)
 
     """
     alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
                                   os.path.isfile)
     if alloc_script is None:
 
     """
     alloc_script = utils.FindFile(name, constants.IALLOCATOR_SEARCH_PATH,
                                   os.path.isfile)
     if alloc_script is None:
-      return (constants.IARUN_NOTFOUND, None, None, None)
+      _Fail("iallocator module '%s' not found in the search path", name)
 
     fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
     try:
 
     fd, fin_name = tempfile.mkstemp(prefix="ganeti-iallocator.")
     try:
@@ -2618,12 +2708,12 @@ class IAllocatorRunner(object):
       os.close(fd)
       result = utils.RunCmd([alloc_script, fin_name])
       if result.failed:
       os.close(fd)
       result = utils.RunCmd([alloc_script, fin_name])
       if result.failed:
-        return (constants.IARUN_FAILURE, result.stdout, result.stderr,
-                result.fail_reason)
+        _Fail("iallocator module '%s' failed: %s, output '%s'",
+              name, result.fail_reason, result.output)
     finally:
       os.unlink(fin_name)
 
     finally:
       os.unlink(fin_name)
 
-    return (constants.IARUN_SUCCESS, result.stdout, result.stderr, None)
+    return result.stdout
 
 
 class DevCacheManager(object):
 
 
 class DevCacheManager(object):
@@ -2684,7 +2774,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):
@@ -2706,4 +2796,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)