Streamlining handling of spindles and default templates
[ganeti-local] / lib / backend.py
index aafe456..a16b22a 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
 #
 #
 
-# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 
 """
 
 
 """
 
-# pylint: disable=E1103
+# pylint: disable=E1103,C0302
 
 # 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
 
 
 # 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
 
+# C0302: This module has become too big and should be split up
+
 
 import os
 import os.path
 
 import os
 import os.path
@@ -54,20 +56,25 @@ from ganeti import utils
 from ganeti import ssh
 from ganeti import hypervisor
 from ganeti import constants
 from ganeti import ssh
 from ganeti import hypervisor
 from ganeti import constants
-from ganeti import bdev
+from ganeti.storage import bdev
+from ganeti.storage import drbd
+from ganeti.storage import filestorage
 from ganeti import objects
 from ganeti import ssconf
 from ganeti import serializer
 from ganeti import netutils
 from ganeti import runtime
 from ganeti import objects
 from ganeti import ssconf
 from ganeti import serializer
 from ganeti import netutils
 from ganeti import runtime
-from ganeti import mcpu
 from ganeti import compat
 from ganeti import pathutils
 from ganeti import vcluster
 from ganeti import compat
 from ganeti import pathutils
 from ganeti import vcluster
+from ganeti import ht
+from ganeti.storage.base import BlockDev
+from ganeti.storage.drbd import DRBD8
+from ganeti import hooksmaster
 
 
 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
 
 
 _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
-_ALLOWED_CLEAN_DIRS = frozenset([
+_ALLOWED_CLEAN_DIRS = compat.UniqueFrozenset([
   pathutils.DATA_DIR,
   pathutils.JOB_QUEUE_ARCHIVE_DIR,
   pathutils.QUEUE_DIR,
   pathutils.DATA_DIR,
   pathutils.JOB_QUEUE_ARCHIVE_DIR,
   pathutils.QUEUE_DIR,
@@ -81,12 +88,25 @@ _IES_PID_FILE = "pid"
 _IES_CA_FILE = "ca"
 
 #: Valid LVS output line regex
 _IES_CA_FILE = "ca"
 
 #: Valid LVS output line regex
-_LVSLINE_REGEX = re.compile("^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
+_LVSLINE_REGEX = re.compile(r"^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
 
 # Actions for the master setup script
 _MASTER_START = "start"
 _MASTER_STOP = "stop"
 
 
 # Actions for the master setup script
 _MASTER_START = "start"
 _MASTER_STOP = "stop"
 
+#: Maximum file permissions for restricted command directory and executables
+_RCMD_MAX_MODE = (stat.S_IRWXU |
+                  stat.S_IRGRP | stat.S_IXGRP |
+                  stat.S_IROTH | stat.S_IXOTH)
+
+#: Delay before returning an error for restricted commands
+_RCMD_INVALID_DELAY = 10
+
+#: How long to wait to acquire lock for restricted commands (shorter than
+#: L{_RCMD_INVALID_DELAY}) to reduce blockage of noded forks when many
+#: command requests arrive
+_RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8
+
 
 class RPCFail(Exception):
   """Class denoting RPC failure.
 
 class RPCFail(Exception):
   """Class denoting RPC failure.
@@ -96,6 +116,34 @@ class RPCFail(Exception):
   """
 
 
   """
 
 
+def _GetInstReasonFilename(instance_name):
+  """Path of the file containing the reason of the instance status change.
+
+  @type instance_name: string
+  @param instance_name: The name of the instance
+  @rtype: string
+  @return: The path of the file
+
+  """
+  return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)
+
+
+def _StoreInstReasonTrail(instance_name, trail):
+  """Serialize a reason trail related to an instance change of state to file.
+
+  The exact location of the file depends on the name of the instance and on
+  the configuration of the Ganeti cluster defined at deploy time.
+
+  @type instance_name: string
+  @param instance_name: The name of the instance
+  @rtype: None
+
+  """
+  json = serializer.DumpJson(trail)
+  filename = _GetInstReasonFilename(instance_name)
+  utils.WriteFile(filename, data=json)
+
+
 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.
 
@@ -200,7 +248,7 @@ def _BuildUploadFileList():
   """
   allowed_files = set([
     pathutils.CLUSTER_CONF_FILE,
   """
   allowed_files = set([
     pathutils.CLUSTER_CONF_FILE,
-    constants.ETC_HOSTS,
+    pathutils.ETC_HOSTS,
     pathutils.SSH_KNOWN_HOSTS_FILE,
     pathutils.VNC_PASSWORD_FILE,
     pathutils.RAPI_CERT_FILE,
     pathutils.SSH_KNOWN_HOSTS_FILE,
     pathutils.VNC_PASSWORD_FILE,
     pathutils.RAPI_CERT_FILE,
@@ -283,10 +331,10 @@ def RunLocalHooks(hook_opcode, hooks_path, env_builder_fn):
 
       cfg = _GetConfig()
       hr = HooksRunner()
 
       cfg = _GetConfig()
       hr = HooksRunner()
-      hm = mcpu.HooksMaster(hook_opcode, hooks_path, nodes, hr.RunLocalHooks,
-                            None, env_fn, logging.warning, cfg.GetClusterName(),
-                            cfg.GetMasterNode())
-
+      hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
+                                   hr.RunLocalHooks, None, env_fn,
+                                   logging.warning, cfg.GetClusterName(),
+                                   cfg.GetMasterNode())
       hm.RunPhase(constants.HOOKS_PHASE_PRE)
       result = fn(*args, **kwargs)
       hm.RunPhase(constants.HOOKS_PHASE_POST)
       hm.RunPhase(constants.HOOKS_PHASE_PRE)
       result = fn(*args, **kwargs)
       hm.RunPhase(constants.HOOKS_PHASE_POST)
@@ -344,8 +392,8 @@ def _RunMasterSetupScript(master_params, action, use_external_mip_script):
   result = utils.RunCmd([setup_script, action], env=env, reset_env=True)
 
   if result.failed:
   result = utils.RunCmd([setup_script, action], env=env, reset_env=True)
 
   if result.failed:
-    _Fail("Failed to %s the master IP. Script return value: %s" %
-          (action, result.exit_code), log=True)
+    _Fail("Failed to %s the master IP. Script return value: %s, output: '%s'" %
+          (action, result.exit_code, result.output), log=True)
 
 
 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup",
 
 
 @RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup",
@@ -527,12 +575,62 @@ def LeaveCluster(modify_ssh_setup):
   raise errors.QuitGanetiException(True, "Shutdown scheduled")
 
 
   raise errors.QuitGanetiException(True, "Shutdown scheduled")
 
 
-def _GetVgInfo(name):
+def _CheckStorageParams(params, num_params):
+  """Performs sanity checks for storage parameters.
+
+  @type params: list
+  @param params: list of storage parameters
+  @type num_params: int
+  @param num_params: expected number of parameters
+
+  """
+  if params is None:
+    raise errors.ProgrammerError("No storage parameters for storage"
+                                 " reporting is provided.")
+  if not isinstance(params, list):
+    raise errors.ProgrammerError("The storage parameters are not of type"
+                                 " list: '%s'" % params)
+  if not len(params) == num_params:
+    raise errors.ProgrammerError("Did not receive the expected number of"
+                                 "storage parameters: expected %s,"
+                                 " received '%s'" % (num_params, len(params)))
+
+
+def _CheckLvmStorageParams(params):
+  """Performs sanity check for the 'exclusive storage' flag.
+
+  @see: C{_CheckStorageParams}
+
+  """
+  _CheckStorageParams(params, 1)
+  excl_stor = params[0]
+  if not isinstance(params[0], bool):
+    raise errors.ProgrammerError("Exclusive storage parameter is not"
+                                 " boolean: '%s'." % excl_stor)
+  return excl_stor
+
+
+def _GetLvmVgSpaceInfo(name, params):
+  """Wrapper around C{_GetVgInfo} which checks the storage parameters.
+
+  @type name: string
+  @param name: name of the volume group
+  @type params: list
+  @param params: list of storage parameters, which in this case should be
+    containing only one for exclusive storage
+
+  """
+  excl_stor = _CheckLvmStorageParams(params)
+  return _GetVgInfo(name, excl_stor)
+
+
+def _GetVgInfo(
+    name, excl_stor, info_fn=bdev.LogicalVolume.GetVGInfo):
   """Retrieves information about a LVM volume group.
 
   """
   # TODO: GetVGInfo supports returning information for multiple VGs at once
   """Retrieves information about a LVM volume group.
 
   """
   # TODO: GetVGInfo supports returning information for multiple VGs at once
-  vginfo = bdev.LogicalVolume.GetVGInfo([name])
+  vginfo = info_fn([name], excl_stor)
   if vginfo:
     vg_free = int(round(vginfo[0][0], 0))
     vg_size = int(round(vginfo[0][1], 0))
   if vginfo:
     vg_free = int(round(vginfo[0][0], 0))
     vg_size = int(round(vginfo[0][1], 0))
@@ -541,13 +639,50 @@ def _GetVgInfo(name):
     vg_size = None
 
   return {
     vg_size = None
 
   return {
+    "type": constants.ST_LVM_VG,
+    "name": name,
+    "storage_free": vg_free,
+    "storage_size": vg_size,
+    }
+
+
+def _GetLvmPvSpaceInfo(name, params):
+  """Wrapper around C{_GetVgSpindlesInfo} with sanity checks.
+
+  @see: C{_GetLvmVgSpaceInfo}
+
+  """
+  excl_stor = _CheckLvmStorageParams(params)
+  return _GetVgSpindlesInfo(name, excl_stor)
+
+
+def _GetVgSpindlesInfo(
+    name, excl_stor, info_fn=bdev.LogicalVolume.GetVgSpindlesInfo):
+  """Retrieves information about spindles in an LVM volume group.
+
+  @type name: string
+  @param name: VG name
+  @type excl_stor: bool
+  @param excl_stor: exclusive storage
+  @rtype: dict
+  @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name,
+      free spindles, total spindles respectively
+
+  """
+  if excl_stor:
+    (vg_free, vg_size) = info_fn(name)
+  else:
+    vg_free = 0
+    vg_size = 0
+  return {
+    "type": constants.ST_LVM_PV,
     "name": name,
     "name": name,
-    "vg_free": vg_free,
-    "vg_size": vg_size,
+    "storage_free": vg_free,
+    "storage_size": vg_size,
     }
 
 
     }
 
 
-def _GetHvInfo(name):
+def _GetHvInfo(name, hvparams, get_hv_fn=hypervisor.GetHypervisor):
   """Retrieves node information from a hypervisor.
 
   The information returned depends on the hypervisor. Common items:
   """Retrieves node information from a hypervisor.
 
   The information returned depends on the hypervisor. Common items:
@@ -559,8 +694,29 @@ def _GetHvInfo(name):
     - memory_total is the total number of ram in MiB
     - hv_version: the hypervisor version, if available
 
     - memory_total is the total number of ram in MiB
     - hv_version: the hypervisor version, if available
 
+  @type hvparams: dict of string
+  @param hvparams: the hypervisor's hvparams
+
+  """
+  return get_hv_fn(name).GetNodeInfo(hvparams=hvparams)
+
+
+def _GetHvInfoAll(hv_specs, get_hv_fn=hypervisor.GetHypervisor):
+  """Retrieves node information for all hypervisors.
+
+  See C{_GetHvInfo} for information on the output.
+
+  @type hv_specs: list of pairs (string, dict of strings)
+  @param hv_specs: list of pairs of a hypervisor's name and its hvparams
+
   """
   """
-  return hypervisor.GetHypervisor(name).GetNodeInfo()
+  if hv_specs is None:
+    return None
+
+  result = []
+  for hvname, hvparams in hv_specs:
+    result.append(_GetHvInfo(hvname, hvparams, get_hv_fn))
+  return result
 
 
 def _GetNamedNodeInfo(names, fn):
 
 
 def _GetNamedNodeInfo(names, fn):
@@ -575,26 +731,205 @@ def _GetNamedNodeInfo(names, fn):
     return map(fn, names)
 
 
     return map(fn, names)
 
 
-def GetNodeInfo(vg_names, hv_names):
+def GetNodeInfo(storage_units, hv_specs):
   """Gives back a hash with different information about the node.
 
   """Gives back a hash with different information about the node.
 
-  @type vg_names: list of string
-  @param vg_names: Names of the volume groups to ask for disk space information
-  @type hv_names: list of string
-  @param hv_names: Names of the hypervisors to ask for node information
+  @type storage_units: list of tuples (string, string, list)
+  @param storage_units: List of tuples (storage unit, identifier, parameters) to
+    ask for disk space information. In case of lvm-vg, the identifier is
+    the VG name. The parameters can contain additional, storage-type-specific
+    parameters, for example exclusive storage for lvm storage.
+  @type hv_specs: list of pairs (string, dict of strings)
+  @param hv_specs: list of pairs of a hypervisor's name and its hvparams
   @rtype: tuple; (string, None/dict, None/dict)
   @return: Tuple containing boot ID, volume group information and hypervisor
     information
 
   """
   bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
   @rtype: tuple; (string, None/dict, None/dict)
   @return: Tuple containing boot ID, volume group information and hypervisor
     information
 
   """
   bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
-  vg_info = _GetNamedNodeInfo(vg_names, _GetVgInfo)
-  hv_info = _GetNamedNodeInfo(hv_names, _GetHvInfo)
+  storage_info = _GetNamedNodeInfo(
+    storage_units,
+    (lambda (storage_type, storage_key, storage_params):
+        _ApplyStorageInfoFunction(storage_type, storage_key, storage_params)))
+  hv_info = _GetHvInfoAll(hv_specs)
+  return (bootid, storage_info, hv_info)
+
+
+def _GetFileStorageSpaceInfo(path, params):
+  """Wrapper around filestorage.GetSpaceInfo.
+
+  The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo
+  and ignore the *args parameter to not leak it into the filestorage
+  module's code.
+
+  @see: C{filestorage.GetFileStorageSpaceInfo} for description of the
+    parameters.
+
+  """
+  _CheckStorageParams(params, 0)
+  return filestorage.GetFileStorageSpaceInfo(path)
+
+
+# FIXME: implement storage reporting for all missing storage types.
+_STORAGE_TYPE_INFO_FN = {
+  constants.ST_BLOCK: None,
+  constants.ST_DISKLESS: None,
+  constants.ST_EXT: None,
+  constants.ST_FILE: _GetFileStorageSpaceInfo,
+  constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
+  constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
+  constants.ST_RADOS: None,
+}
+
+
+def _ApplyStorageInfoFunction(storage_type, storage_key, *args):
+  """Looks up and applies the correct function to calculate free and total
+  storage for the given storage type.
+
+  @type storage_type: string
+  @param storage_type: the storage type for which the storage shall be reported.
+  @type storage_key: string
+  @param storage_key: identifier of a storage unit, e.g. the volume group name
+    of an LVM storage unit
+  @type args: any
+  @param args: various parameters that can be used for storage reporting. These
+    parameters and their semantics vary from storage type to storage type and
+    are just propagated in this function.
+  @return: the results of the application of the storage space function (see
+    _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that
+    storage type
+  @raises NotImplementedError: for storage types who don't support space
+    reporting yet
+  """
+  fn = _STORAGE_TYPE_INFO_FN[storage_type]
+  if fn is not None:
+    return fn(storage_key, *args)
+  else:
+    raise NotImplementedError
+
+
+def _CheckExclusivePvs(pvi_list):
+  """Check that PVs are not shared among LVs
+
+  @type pvi_list: list of L{objects.LvmPvInfo} objects
+  @param pvi_list: information about the PVs
+
+  @rtype: list of tuples (string, list of strings)
+  @return: offending volumes, as tuples: (pv_name, [lv1_name, lv2_name...])
+
+  """
+  res = []
+  for pvi in pvi_list:
+    if len(pvi.lv_list) > 1:
+      res.append((pvi.name, pvi.lv_list))
+  return res
+
 
 
-  return (bootid, vg_info, hv_info)
+def _VerifyHypervisors(what, vm_capable, result, all_hvparams,
+                       get_hv_fn=hypervisor.GetHypervisor):
+  """Verifies the hypervisor. Appends the results to the 'results' list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+  @type get_hv_fn: function
+  @param get_hv_fn: function to retrieve the hypervisor, to improve testability
+
+  """
+  if not vm_capable:
+    return
+
+  if constants.NV_HYPERVISOR in what:
+    result[constants.NV_HYPERVISOR] = {}
+    for hv_name in what[constants.NV_HYPERVISOR]:
+      hvparams = all_hvparams[hv_name]
+      try:
+        val = get_hv_fn(hv_name).Verify(hvparams=hvparams)
+      except errors.HypervisorError, err:
+        val = "Error while checking hypervisor: %s" % str(err)
+      result[constants.NV_HYPERVISOR][hv_name] = val
+
+
+def _VerifyHvparams(what, vm_capable, result,
+                    get_hv_fn=hypervisor.GetHypervisor):
+  """Verifies the hvparams. Appends the results to the 'results' list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type get_hv_fn: function
+  @param get_hv_fn: function to retrieve the hypervisor, to improve testability
+
+  """
+  if not vm_capable:
+    return
+
+  if constants.NV_HVPARAMS in what:
+    result[constants.NV_HVPARAMS] = []
+    for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
+      try:
+        logging.info("Validating hv %s, %s", hv_name, hvparms)
+        get_hv_fn(hv_name).ValidateParameters(hvparms)
+      except errors.HypervisorError, err:
+        result[constants.NV_HVPARAMS].append((source, hv_name, str(err)))
+
+
+def _VerifyInstanceList(what, vm_capable, result, all_hvparams):
+  """Verifies the instance list.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+
+  """
+  if constants.NV_INSTANCELIST in what and vm_capable:
+    # GetInstanceList can fail
+    try:
+      val = GetInstanceList(what[constants.NV_INSTANCELIST],
+                            all_hvparams=all_hvparams)
+    except RPCFail, err:
+      val = str(err)
+    result[constants.NV_INSTANCELIST] = val
+
+
+def _VerifyNodeInfo(what, vm_capable, result, all_hvparams):
+  """Verifies the node info.
+
+  @type what: C{dict}
+  @param what: a dictionary of things to check
+  @type vm_capable: boolean
+  @param vm_capable: whether or not this node is vm capable
+  @type result: dict
+  @param result: dictionary of verification results; results of the
+    verifications in this function will be added here
+  @type all_hvparams: dict of dict of string
+  @param all_hvparams: dictionary mapping hypervisor names to hvparams
+
+  """
+  if constants.NV_HVINFO in what and vm_capable:
+    hvname = what[constants.NV_HVINFO]
+    hyper = hypervisor.GetHypervisor(hvname)
+    hvparams = all_hvparams[hvname]
+    result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)
 
 
 
 
-def VerifyNode(what, cluster_name):
+def VerifyNode(what, cluster_name, all_hvparams):
   """Verify the status of the local node.
 
   Based on the input L{what} parameter, various checks are done on the
   """Verify the status of the local node.
 
   Based on the input L{what} parameter, various checks are done on the
@@ -618,6 +953,10 @@ def VerifyNode(what, cluster_name):
       - node-net-test: list of nodes we should check node daemon port
         connectivity with
       - hypervisor: list with hypervisors to run the verify for
       - node-net-test: list of nodes we should check node daemon port
         connectivity with
       - hypervisor: list with hypervisors to run the verify for
+  @type cluster_name: string
+  @param cluster_name: the cluster's name
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: a dictionary mapping hypervisor names to hvparams
   @rtype: dict
   @return: a dictionary with the same keys as the input dict, and
       values representing the result of the checks
   @rtype: dict
   @return: a dictionary with the same keys as the input dict, and
       values representing the result of the checks
@@ -628,27 +967,15 @@ def VerifyNode(what, cluster_name):
   port = netutils.GetDaemonPort(constants.NODED)
   vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
 
   port = netutils.GetDaemonPort(constants.NODED)
   vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
 
-  if constants.NV_HYPERVISOR in what and vm_capable:
-    result[constants.NV_HYPERVISOR] = tmp = {}
-    for hv_name in what[constants.NV_HYPERVISOR]:
-      try:
-        val = hypervisor.GetHypervisor(hv_name).Verify()
-      except errors.HypervisorError, err:
-        val = "Error while checking hypervisor: %s" % str(err)
-      tmp[hv_name] = val
-
-  if constants.NV_HVPARAMS in what and vm_capable:
-    result[constants.NV_HVPARAMS] = tmp = []
-    for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
-      try:
-        logging.info("Validating hv %s, %s", hv_name, hvparms)
-        hypervisor.GetHypervisor(hv_name).ValidateParameters(hvparms)
-      except errors.HypervisorError, err:
-        tmp.append((source, hv_name, str(err)))
+  _VerifyHypervisors(what, vm_capable, result, all_hvparams)
+  _VerifyHvparams(what, vm_capable, result)
 
   if constants.NV_FILELIST in what:
 
   if constants.NV_FILELIST in what:
-    result[constants.NV_FILELIST] = utils.FingerprintFiles(
-      what[constants.NV_FILELIST])
+    fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath,
+                                              what[constants.NV_FILELIST]))
+    result[constants.NV_FILELIST] = \
+      dict((vcluster.MakeVirtualPath(key), value)
+           for (key, value) in fingerprints.items())
 
   if constants.NV_NODELIST in what:
     (nodes, bynode) = what[constants.NV_NODELIST]
 
   if constants.NV_NODELIST in what:
     (nodes, bynode) = what[constants.NV_NODELIST]
@@ -708,7 +1035,7 @@ def VerifyNode(what, cluster_name):
   if constants.NV_USERSCRIPTS in what:
     result[constants.NV_USERSCRIPTS] = \
       [script for script in what[constants.NV_USERSCRIPTS]
   if constants.NV_USERSCRIPTS in what:
     result[constants.NV_USERSCRIPTS] = \
       [script for script in what[constants.NV_USERSCRIPTS]
-       if not (os.path.exists(script) and os.access(script, os.X_OK))]
+       if not utils.IsExecutable(script)]
 
   if constants.NV_OOB_PATHS in what:
     result[constants.NV_OOB_PATHS] = tmp = []
 
   if constants.NV_OOB_PATHS in what:
     result[constants.NV_OOB_PATHS] = tmp = []
@@ -733,33 +1060,40 @@ def VerifyNode(what, cluster_name):
       val = str(err)
     result[constants.NV_LVLIST] = val
 
       val = str(err)
     result[constants.NV_LVLIST] = val
 
-  if constants.NV_INSTANCELIST in what and vm_capable:
-    # GetInstanceList can fail
-    try:
-      val = GetInstanceList(what[constants.NV_INSTANCELIST])
-    except RPCFail, err:
-      val = str(err)
-    result[constants.NV_INSTANCELIST] = val
+  _VerifyInstanceList(what, vm_capable, result, all_hvparams)
 
   if constants.NV_VGLIST in what and vm_capable:
     result[constants.NV_VGLIST] = utils.ListVolumeGroups()
 
   if constants.NV_PVLIST in what and vm_capable:
 
   if constants.NV_VGLIST in what and vm_capable:
     result[constants.NV_VGLIST] = utils.ListVolumeGroups()
 
   if constants.NV_PVLIST in what and vm_capable:
-    result[constants.NV_PVLIST] = \
-      bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
-                                   filter_allocatable=False)
+    check_exclusive_pvs = constants.NV_EXCLUSIVEPVS in what
+    val = bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
+                                       filter_allocatable=False,
+                                       include_lvs=check_exclusive_pvs)
+    if check_exclusive_pvs:
+      result[constants.NV_EXCLUSIVEPVS] = _CheckExclusivePvs(val)
+      for pvi in val:
+        # Avoid sending useless data on the wire
+        pvi.lv_list = []
+    result[constants.NV_PVLIST] = map(objects.LvmPvInfo.ToDict, val)
 
   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)
 
-  if constants.NV_HVINFO in what and vm_capable:
-    hyper = hypervisor.GetHypervisor(what[constants.NV_HVINFO])
-    result[constants.NV_HVINFO] = hyper.GetNodeInfo()
+  _VerifyNodeInfo(what, vm_capable, result, all_hvparams)
+
+  if constants.NV_DRBDVERSION in what and vm_capable:
+    try:
+      drbd_version = DRBD8.GetProcInfo().GetVersionString()
+    except errors.BlockDeviceError, err:
+      logging.warning("Can't get DRBD version", exc_info=True)
+      drbd_version = str(err)
+    result[constants.NV_DRBDVERSION] = drbd_version
 
   if constants.NV_DRBDLIST in what and vm_capable:
     try:
 
   if constants.NV_DRBDLIST in what and vm_capable:
     try:
-      used_minors = bdev.DRBD8.GetUsedDevs().keys()
+      used_minors = drbd.DRBD8.GetUsedDevs()
     except errors.BlockDeviceError, err:
       logging.warning("Can't get used minors list", exc_info=True)
       used_minors = str(err)
     except errors.BlockDeviceError, err:
       logging.warning("Can't get used minors list", exc_info=True)
       used_minors = str(err)
@@ -768,7 +1102,7 @@ def VerifyNode(what, cluster_name):
   if constants.NV_DRBDHELPER in what and vm_capable:
     status = True
     try:
   if constants.NV_DRBDHELPER in what and vm_capable:
     status = True
     try:
-      payload = bdev.BaseDRBD.GetUsermodeHelper()
+      payload = drbd.DRBD8.GetUsermodeHelper()
     except errors.BlockDeviceError, err:
       logging.error("Can't get DRBD usermode helper: %s", str(err))
       status = False
     except errors.BlockDeviceError, err:
       logging.error("Can't get DRBD usermode helper: %s", str(err))
       status = False
@@ -797,6 +1131,23 @@ def VerifyNode(what, cluster_name):
     result[constants.NV_BRIDGES] = [bridge
                                     for bridge in what[constants.NV_BRIDGES]
                                     if not utils.BridgeExists(bridge)]
     result[constants.NV_BRIDGES] = [bridge
                                     for bridge in what[constants.NV_BRIDGES]
                                     if not utils.BridgeExists(bridge)]
+
+  if what.get(constants.NV_ACCEPTED_STORAGE_PATHS) == my_name:
+    result[constants.NV_ACCEPTED_STORAGE_PATHS] = \
+        filestorage.ComputeWrongFileStoragePaths()
+
+  if what.get(constants.NV_FILE_STORAGE_PATH):
+    pathresult = filestorage.CheckFileStoragePath(
+        what[constants.NV_FILE_STORAGE_PATH])
+    if pathresult:
+      result[constants.NV_FILE_STORAGE_PATH] = pathresult
+
+  if what.get(constants.NV_SHARED_FILE_STORAGE_PATH):
+    pathresult = filestorage.CheckFileStoragePath(
+        what[constants.NV_SHARED_FILE_STORAGE_PATH])
+    if pathresult:
+      result[constants.NV_SHARED_FILE_STORAGE_PATH] = pathresult
+
   return result
 
 
   return result
 
 
@@ -957,11 +1308,47 @@ def BridgesExist(bridges_list):
     _Fail("Missing bridges %s", utils.CommaJoin(missing))
 
 
     _Fail("Missing bridges %s", utils.CommaJoin(missing))
 
 
-def GetInstanceList(hypervisor_list):
+def GetInstanceListForHypervisor(hname, hvparams=None,
+                                 get_hv_fn=hypervisor.GetHypervisor):
+  """Provides a list of instances of the given hypervisor.
+
+  @type hname: string
+  @param hname: name of the hypervisor
+  @type hvparams: dict of strings
+  @param hvparams: hypervisor parameters for the given hypervisor
+  @type get_hv_fn: function
+  @param get_hv_fn: function that returns a hypervisor for the given hypervisor
+    name; optional parameter to increase testability
+
+  @rtype: list
+  @return: a list of all running instances on the current node
+    - instance1.example.com
+    - instance2.example.com
+
+  """
+  results = []
+  try:
+    hv = get_hv_fn(hname)
+    names = hv.ListInstances(hvparams=hvparams)
+    results.extend(names)
+  except errors.HypervisorError, err:
+    _Fail("Error enumerating instances (hypervisor %s): %s",
+          hname, err, exc=True)
+  return results
+
+
+def GetInstanceList(hypervisor_list, all_hvparams=None,
+                    get_hv_fn=hypervisor.GetHypervisor):
   """Provides a list of instances.
 
   @type hypervisor_list: list
   @param hypervisor_list: the list of hypervisors to query information
   """Provides a list of instances.
 
   @type hypervisor_list: list
   @param hypervisor_list: the list of hypervisors to query information
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: a dictionary mapping hypervisor types to respective
+    cluster-wide hypervisor parameters
+  @type get_hv_fn: function
+  @param get_hv_fn: function that returns a hypervisor for the given hypervisor
+    name; optional parameter to increase testability
 
   @rtype: list
   @return: a list of all running instances on the current node
 
   @rtype: list
   @return: a list of all running instances on the current node
@@ -971,23 +1358,21 @@ def GetInstanceList(hypervisor_list):
   """
   results = []
   for hname in hypervisor_list:
   """
   results = []
   for hname in hypervisor_list:
-    try:
-      names = hypervisor.GetHypervisor(hname).ListInstances()
-      results.extend(names)
-    except errors.HypervisorError, err:
-      _Fail("Error enumerating instances (hypervisor %s): %s",
-            hname, err, exc=True)
-
+    hvparams = all_hvparams[hname]
+    results.extend(GetInstanceListForHypervisor(hname, hvparams=hvparams,
+                                                get_hv_fn=get_hv_fn))
   return results
 
 
   return results
 
 
-def GetInstanceInfo(instance, hname):
+def GetInstanceInfo(instance, hname, hvparams=None):
   """Gives back the information about an instance as a dictionary.
 
   @type instance: string
   @param instance: the instance name
   @type hname: string
   @param hname: the hypervisor type of the instance
   """Gives back the information about an instance as a dictionary.
 
   @type instance: string
   @param instance: the instance name
   @type hname: string
   @param hname: the hypervisor type of the instance
+  @type hvparams: dict of strings
+  @param hvparams: the instance's hvparams
 
   @rtype: dict
   @return: dictionary with the following keys:
 
   @rtype: dict
   @return: dictionary with the following keys:
@@ -999,7 +1384,8 @@ def GetInstanceInfo(instance, hname):
   """
   output = {}
 
   """
   output = {}
 
-  iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance)
+  iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance,
+                                                          hvparams=hvparams)
   if iinfo is not None:
     output["memory"] = iinfo[2]
     output["vcpus"] = iinfo[3]
   if iinfo is not None:
     output["memory"] = iinfo[2]
     output["vcpus"] = iinfo[3]
@@ -1010,7 +1396,7 @@ def GetInstanceInfo(instance, hname):
 
 
 def GetInstanceMigratable(instance):
 
 
 def GetInstanceMigratable(instance):
-  """Gives whether an instance can be migrated.
+  """Computes whether an instance can be migrated.
 
   @type instance: L{objects.Instance}
   @param instance: object representing the instance to be checked.
 
   @type instance: L{objects.Instance}
   @param instance: object representing the instance to be checked.
@@ -1023,7 +1409,7 @@ def GetInstanceMigratable(instance):
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
   iname = instance.name
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
   iname = instance.name
-  if iname not in hyper.ListInstances():
+  if iname not in hyper.ListInstances(instance.hvparams):
     _Fail("Instance %s is not running", iname)
 
   for idx in range(len(instance.disks)):
     _Fail("Instance %s is not running", iname)
 
   for idx in range(len(instance.disks)):
@@ -1033,7 +1419,7 @@ def GetInstanceMigratable(instance):
                       iname, link_name, idx)
 
 
                       iname, link_name, idx)
 
 
-def GetAllInstancesInfo(hypervisor_list):
+def GetAllInstancesInfo(hypervisor_list, all_hvparams):
   """Gather data about all instances.
 
   This is the equivalent of L{GetInstanceInfo}, except that it
   """Gather data about all instances.
 
   This is the equivalent of L{GetInstanceInfo}, except that it
@@ -1042,6 +1428,8 @@ def GetAllInstancesInfo(hypervisor_list):
 
   @type hypervisor_list: list
   @param hypervisor_list: list of hypervisors to query for instance data
 
   @type hypervisor_list: list
   @param hypervisor_list: list of hypervisors to query for instance data
+  @type all_hvparams: dict of dict of strings
+  @param all_hvparams: mapping of hypervisor names to hvparams
 
   @rtype: dict
   @return: dictionary of instance: data, with data having the following keys:
 
   @rtype: dict
   @return: dictionary of instance: data, with data having the following keys:
@@ -1054,7 +1442,8 @@ def GetAllInstancesInfo(hypervisor_list):
   output = {}
 
   for hname in hypervisor_list:
   output = {}
 
   for hname in hypervisor_list:
-    iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo()
+    hvparams = all_hvparams[hname]
+    iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams)
     if iinfo:
       for name, _, memory, vcpus, state, times in iinfo:
         value = {
     if iinfo:
       for name, _, memory, vcpus, state, times in iinfo:
         value = {
@@ -1169,9 +1558,16 @@ def RunRenameInstance(instance, old_name, debug):
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
 
           " log file:\n%s", result.fail_reason, "\n".join(lines), log=False)
 
 
-def _GetBlockDevSymlinkPath(instance_name, idx):
-  return utils.PathJoin(pathutils.DISK_LINKS_DIR, "%s%s%d" %
-                        (instance_name, constants.DISK_SEPARATOR, idx))
+def _GetBlockDevSymlinkPath(instance_name, idx, _dir=None):
+  """Returns symlink path for block device.
+
+  """
+  if _dir is None:
+    _dir = pathutils.DISK_LINKS_DIR
+
+  return utils.PathJoin(_dir,
+                        ("%s%s%s" %
+                         (instance_name, constants.DISK_SEPARATOR, idx)))
 
 
 def _SymlinkBlockDev(instance_name, device_path, idx):
 
 
 def _SymlinkBlockDev(instance_name, device_path, idx):
@@ -1222,7 +1618,7 @@ def _GatherAndLinkBlockDevs(instance):
   devices must be already assembled.
 
   @type instance: L{objects.Instance}
   devices must be already assembled.
 
   @type instance: L{objects.Instance}
-  @param instance: the instance whose disks we shoul assemble
+  @param instance: the instance whose disks we should assemble
   @rtype: list
   @return: list of (disk_object, device_path)
 
   @rtype: list
   @return: list of (disk_object, device_path)
 
@@ -1240,22 +1636,27 @@ def _GatherAndLinkBlockDevs(instance):
       raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
                                     e.strerror)
 
       raise errors.BlockDeviceError("Cannot create block device symlink: %s" %
                                     e.strerror)
 
-    block_devices.append((disk, link_name))
+    block_devices.append((disk, link_name, device))
 
   return block_devices
 
 
 
   return block_devices
 
 
-def StartInstance(instance, startup_paused):
+def StartInstance(instance, startup_paused, reason, store_reason=True):
   """Start an instance.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
   @type startup_paused: bool
   @param instance: pause instance at startup?
   """Start an instance.
 
   @type instance: L{objects.Instance}
   @param instance: the instance object
   @type startup_paused: bool
   @param instance: pause instance at startup?
+  @type reason: list of reasons
+  @param reason: the reason trail for this startup
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
   @rtype: None
 
   """
-  running_instances = GetInstanceList([instance.hypervisor])
+  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
+                                                   instance.hvparams)
 
   if instance.name in running_instances:
     logging.info("Instance %s already running, not starting", instance.name)
 
   if instance.name in running_instances:
     logging.info("Instance %s already running, not starting", instance.name)
@@ -1265,6 +1666,8 @@ def StartInstance(instance, startup_paused):
     block_devices = _GatherAndLinkBlockDevs(instance)
     hyper = hypervisor.GetHypervisor(instance.hypervisor)
     hyper.StartInstance(instance, block_devices, startup_paused)
     block_devices = _GatherAndLinkBlockDevs(instance)
     hyper = hypervisor.GetHypervisor(instance.hypervisor)
     hyper.StartInstance(instance, block_devices, startup_paused)
+    if store_reason:
+      _StoreInstReasonTrail(instance.name, reason)
   except errors.BlockDeviceError, err:
     _Fail("Block device error: %s", err, exc=True)
   except errors.HypervisorError, err:
   except errors.BlockDeviceError, err:
     _Fail("Block device error: %s", err, exc=True)
   except errors.HypervisorError, err:
@@ -1272,7 +1675,7 @@ def StartInstance(instance, startup_paused):
     _Fail("Hypervisor error: %s", err, exc=True)
 
 
     _Fail("Hypervisor error: %s", err, exc=True)
 
 
-def InstanceShutdown(instance, timeout):
+def InstanceShutdown(instance, timeout, reason, store_reason=True):
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
   """Shut an instance down.
 
   @note: this functions uses polling with a hardcoded timeout.
@@ -1281,6 +1684,10 @@ def InstanceShutdown(instance, timeout):
   @param instance: the instance object
   @type timeout: integer
   @param timeout: maximum timeout for soft shutdown
   @param instance: the instance object
   @type timeout: integer
   @param timeout: maximum timeout for soft shutdown
+  @type reason: list of reasons
+  @param reason: the reason trail for this shutdown
+  @type store_reason: boolean
+  @param store_reason: whether to store the shutdown reason trail on file
   @rtype: None
 
   """
   @rtype: None
 
   """
@@ -1288,7 +1695,7 @@ def InstanceShutdown(instance, timeout):
   hyper = hypervisor.GetHypervisor(hv_name)
   iname = instance.name
 
   hyper = hypervisor.GetHypervisor(hv_name)
   iname = instance.name
 
-  if instance.name not in hyper.ListInstances():
+  if instance.name not in hyper.ListInstances(instance.hvparams):
     logging.info("Instance %s not running, doing nothing", iname)
     return
 
     logging.info("Instance %s not running, doing nothing", iname)
     return
 
@@ -1297,13 +1704,15 @@ def InstanceShutdown(instance, timeout):
       self.tried_once = False
 
     def __call__(self):
       self.tried_once = False
 
     def __call__(self):
-      if iname not in hyper.ListInstances():
+      if iname not in hyper.ListInstances(instance.hvparams):
         return
 
       try:
         hyper.StopInstance(instance, retry=self.tried_once)
         return
 
       try:
         hyper.StopInstance(instance, retry=self.tried_once)
+        if store_reason:
+          _StoreInstReasonTrail(instance.name, reason)
       except errors.HypervisorError, err:
       except errors.HypervisorError, err:
-        if iname not in hyper.ListInstances():
+        if iname not in hyper.ListInstances(instance.hvparams):
           # if the instance is no longer existing, consider this a
           # success and go to cleanup
           return
           # if the instance is no longer existing, consider this a
           # success and go to cleanup
           return
@@ -1323,14 +1732,14 @@ def InstanceShutdown(instance, timeout):
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
     try:
       hyper.StopInstance(instance, force=True)
     except errors.HypervisorError, err:
-      if iname in hyper.ListInstances():
+      if iname in hyper.ListInstances(instance.hvparams):
         # 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)
 
         # 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)
 
-    if iname in hyper.ListInstances():
+    if iname in hyper.ListInstances(instance.hvparams):
       _Fail("Could not shutdown instance %s even by destroy", iname)
 
   try:
       _Fail("Could not shutdown instance %s even by destroy", iname)
 
   try:
@@ -1341,7 +1750,7 @@ def InstanceShutdown(instance, timeout):
   _RemoveBlockDevLinks(iname, instance.disks)
 
 
   _RemoveBlockDevLinks(iname, instance.disks)
 
 
-def InstanceReboot(instance, reboot_type, shutdown_timeout):
+def InstanceReboot(instance, reboot_type, shutdown_timeout, reason):
   """Reboot an instance.
 
   @type instance: L{objects.Instance}
   """Reboot an instance.
 
   @type instance: L{objects.Instance}
@@ -1359,10 +1768,13 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout):
         instance (instead of a call_instance_reboot RPC)
   @type shutdown_timeout: integer
   @param shutdown_timeout: maximum timeout for soft shutdown
         instance (instead of a call_instance_reboot RPC)
   @type shutdown_timeout: integer
   @param shutdown_timeout: maximum timeout for soft shutdown
+  @type reason: list of reasons
+  @param reason: the reason trail for this reboot
   @rtype: None
 
   """
   @rtype: None
 
   """
-  running_instances = GetInstanceList([instance.hypervisor])
+  running_instances = GetInstanceListForHypervisor(instance.hypervisor,
+                                                   instance.hvparams)
 
   if instance.name not in running_instances:
     _Fail("Cannot reboot instance %s that is not running", instance.name)
 
   if instance.name not in running_instances:
     _Fail("Cannot reboot instance %s that is not running", instance.name)
@@ -1375,8 +1787,10 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout):
       _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:
-      InstanceShutdown(instance, shutdown_timeout)
-      return StartInstance(instance, False)
+      InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False)
+      result = StartInstance(instance, False, reason, store_reason=False)
+      _StoreInstReasonTrail(instance.name, reason)
+      return result
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
   else:
     except errors.HypervisorError, err:
       _Fail("Failed to hard reboot instance %s: %s", instance.name, err)
   else:
@@ -1394,7 +1808,7 @@ def InstanceBalloonMemory(instance, memory):
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   """
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
-  running = hyper.ListInstances()
+  running = hyper.ListInstances(instance.hvparams)
   if instance.name not in running:
     logging.info("Instance %s is not running, cannot balloon", instance.name)
     return
   if instance.name not in running:
     logging.info("Instance %s is not running, cannot balloon", instance.name)
     return
@@ -1466,9 +1880,11 @@ def FinalizeMigrationDst(instance, info, success):
     _Fail("Failed to finalize migration on the target node: %s", err, exc=True)
 
 
     _Fail("Failed to finalize migration on the target node: %s", err, exc=True)
 
 
-def MigrateInstance(instance, target, live):
+def MigrateInstance(cluster_name, instance, target, live):
   """Migrates an instance to another node.
 
   """Migrates an instance to another node.
 
+  @type cluster_name: string
+  @param cluster_name: name of the cluster
   @type instance: L{objects.Instance}
   @param instance: the instance definition
   @type target: string
   @type instance: L{objects.Instance}
   @param instance: the instance definition
   @type target: string
@@ -1482,7 +1898,7 @@ def MigrateInstance(instance, target, live):
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   try:
   hyper = hypervisor.GetHypervisor(instance.hypervisor)
 
   try:
-    hyper.MigrateInstance(instance, target, live)
+    hyper.MigrateInstance(cluster_name, 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)
 
@@ -1527,7 +1943,7 @@ def GetMigrationStatus(instance):
     _Fail("Failed to get migration status: %s", err, exc=True)
 
 
     _Fail("Failed to get migration status: %s", err, exc=True)
 
 
-def BlockdevCreate(disk, size, owner, on_primary, info):
+def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor):
   """Creates a block device for an instance.
 
   @type disk: L{objects.Disk}
   """Creates a block device for an instance.
 
   @type disk: L{objects.Disk}
@@ -1542,6 +1958,8 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
   @type info: string
   @param info: string that will be sent to the physical device
       creation, used for example to set (LVM) tags on LVs
   @type info: string
   @param info: string that will be sent to the physical device
       creation, used for example to set (LVM) tags on LVs
+  @type excl_stor: boolean
+  @param excl_stor: Whether exclusive_storage is active
 
   @return: the new unique_id of the device (this can sometime be
       computed only after creation), or None. On secondary nodes,
 
   @return: the new unique_id of the device (this can sometime be
       computed only after creation), or None. On secondary nodes,
@@ -1568,7 +1986,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info):
       clist.append(crdev)
 
   try:
       clist.append(crdev)
 
   try:
-    device = bdev.Create(disk, clist)
+    device = bdev.Create(disk, clist, excl_stor)
   except errors.BlockDeviceError, err:
     _Fail("Can't create block device: %s", err)
 
   except errors.BlockDeviceError, err:
     _Fail("Can't create block device: %s", err)
 
@@ -1783,7 +2201,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx):
   """
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
   """
   try:
     result = _RecursiveAssembleBD(disk, owner, as_primary)
-    if isinstance(result, bdev.BlockDev):
+    if isinstance(result, BlockDev):
       # pylint: disable=E1103
       result = result.dev_path
       if as_primary:
       # pylint: disable=E1103
       result = result.dev_path
       if as_primary:
@@ -1994,7 +2412,7 @@ def BlockdevFind(disk):
   return rbd.GetSyncStatus()
 
 
   return rbd.GetSyncStatus()
 
 
-def BlockdevGetsize(disks):
+def BlockdevGetdimensions(disks):
   """Computes the size of the given disks.
 
   If a disk is not found, returns None instead.
   """Computes the size of the given disks.
 
   If a disk is not found, returns None instead.
@@ -2003,7 +2421,8 @@ def BlockdevGetsize(disks):
   @param disks: the list of disk to compute the size for
   @rtype: list
   @return: list with elements None if the disk cannot be found,
   @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
+      otherwise the pair (size, spindles), where spindles is None if the
+      device doesn't support that
 
   """
   result = []
 
   """
   result = []
@@ -2016,17 +2435,17 @@ def BlockdevGetsize(disks):
     if rbd is None:
       result.append(None)
     else:
     if rbd is None:
       result.append(None)
     else:
-      result.append(rbd.GetActualSize())
+      result.append(rbd.GetActualDimensions())
   return result
 
 
   return result
 
 
-def BlockdevExport(disk, dest_node, dest_path, cluster_name):
+def BlockdevExport(disk, dest_node_ip, 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
   """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_node_ip: str
+  @param dest_node_ip: the destination node IP to export to
   @type dest_path: str
   @param dest_path: the destination path on the target node
   @type cluster_name: str
   @type dest_path: str
   @param dest_path: the destination path on the target node
   @type cluster_name: str
@@ -2050,7 +2469,7 @@ def BlockdevExport(disk, dest_node, dest_path, cluster_name):
   destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536"
                                 " oflag=dsync", dest_path)
 
   destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536"
                                 " oflag=dsync", dest_path)
 
-  remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node,
+  remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node_ip,
                                                    constants.SSH_LOGIN_USER,
                                                    destcmd)
 
                                                    constants.SSH_LOGIN_USER,
                                                    destcmd)
 
@@ -2288,7 +2707,8 @@ def _TryOSFromDisk(name, base_dir=None):
   if constants.OS_VARIANTS_FILE in os_files:
     variants_file = os_files[constants.OS_VARIANTS_FILE]
     try:
   if constants.OS_VARIANTS_FILE in os_files:
     variants_file = os_files[constants.OS_VARIANTS_FILE]
     try:
-      variants = utils.ReadFile(variants_file).splitlines()
+      variants = \
+        utils.FilterEmptyLinesAndComments(utils.ReadFile(variants_file))
     except EnvironmentError, err:
       # we accept missing files, but not other errors
       if err.errno != errno.ENOENT:
     except EnvironmentError, err:
       # we accept missing files, but not other errors
       if err.errno != errno.ENOENT:
@@ -2421,18 +2841,24 @@ def OSEnvironment(instance, inst_os, debug=0):
     real_disk = _OpenRealBD(disk)
     result["DISK_%d_PATH" % idx] = real_disk.dev_path
     result["DISK_%d_ACCESS" % idx] = disk.mode
     real_disk = _OpenRealBD(disk)
     result["DISK_%d_PATH" % idx] = real_disk.dev_path
     result["DISK_%d_ACCESS" % idx] = disk.mode
+    result["DISK_%d_UUID" % idx] = disk.uuid
+    if disk.name:
+      result["DISK_%d_NAME" % idx] = disk.name
     if constants.HV_DISK_TYPE in instance.hvparams:
       result["DISK_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_DISK_TYPE]
     if constants.HV_DISK_TYPE in instance.hvparams:
       result["DISK_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_DISK_TYPE]
-    if disk.dev_type in constants.LDS_BLOCK:
+    if disk.dev_type in constants.DTS_BLOCK:
       result["DISK_%d_BACKEND_TYPE" % idx] = "block"
       result["DISK_%d_BACKEND_TYPE" % idx] = "block"
-    elif disk.dev_type == constants.LD_FILE:
+    elif disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
       result["DISK_%d_BACKEND_TYPE" % idx] = \
       result["DISK_%d_BACKEND_TYPE" % idx] = \
-        "file:%s" % disk.physical_id[0]
+        "file:%s" % disk.logical_id[0]
 
   # NICs
   for idx, nic in enumerate(instance.nics):
     result["NIC_%d_MAC" % idx] = nic.mac
 
   # NICs
   for idx, nic in enumerate(instance.nics):
     result["NIC_%d_MAC" % idx] = nic.mac
+    result["NIC_%d_UUID" % idx] = nic.uuid
+    if nic.name:
+      result["NIC_%d_NAME" % idx] = nic.name
     if nic.ip:
       result["NIC_%d_IP" % idx] = nic.ip
     result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
     if nic.ip:
       result["NIC_%d_IP" % idx] = nic.ip
     result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE]
@@ -2440,6 +2866,9 @@ def OSEnvironment(instance, inst_os, debug=0):
       result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
     if nic.nicparams[constants.NIC_LINK]:
       result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
       result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK]
     if nic.nicparams[constants.NIC_LINK]:
       result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK]
+    if nic.netinfo:
+      nobj = objects.Network.FromDict(nic.netinfo)
+      result.update(nobj.HooksDict("NIC_%d_" % idx))
     if constants.HV_NIC_TYPE in instance.hvparams:
       result["NIC_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_NIC_TYPE]
     if constants.HV_NIC_TYPE in instance.hvparams:
       result["NIC_%d_FRONTEND_TYPE" % idx] = \
         instance.hvparams[constants.HV_NIC_TYPE]
@@ -2452,7 +2881,52 @@ def OSEnvironment(instance, inst_os, debug=0):
   return result
 
 
   return result
 
 
-def BlockdevGrow(disk, amount, dryrun, backingstore):
+def DiagnoseExtStorage(top_dirs=None):
+  """Compute the validity for all ExtStorage Providers.
+
+  @type top_dirs: list
+  @param top_dirs: the list of directories in which to
+      search (if not given defaults to
+      L{pathutils.ES_SEARCH_PATH})
+  @rtype: list of L{objects.ExtStorage}
+  @return: a list of tuples (name, path, status, diagnose, parameters)
+      for all (potential) ExtStorage Providers under all
+      search paths, where:
+          - name is the (potential) ExtStorage Provider
+          - path is the full path to the ExtStorage Provider
+          - status True/False is the validity of the ExtStorage Provider
+          - diagnose is the error message for an invalid ExtStorage Provider,
+            otherwise empty
+          - parameters is a list of (name, help) parameters, if any
+
+  """
+  if top_dirs is None:
+    top_dirs = pathutils.ES_SEARCH_PATH
+
+  result = []
+  for dir_name in top_dirs:
+    if os.path.isdir(dir_name):
+      try:
+        f_names = utils.ListVisibleFiles(dir_name)
+      except EnvironmentError, err:
+        logging.exception("Can't list the ExtStorage directory %s: %s",
+                          dir_name, err)
+        break
+      for name in f_names:
+        es_path = utils.PathJoin(dir_name, name)
+        status, es_inst = bdev.ExtStorageFromDisk(name, base_dir=dir_name)
+        if status:
+          diagnose = ""
+          parameters = es_inst.supported_parameters
+        else:
+          diagnose = es_inst
+          parameters = []
+        result.append((name, es_path, status, diagnose, parameters))
+
+  return result
+
+
+def BlockdevGrow(disk, amount, dryrun, backingstore, excl_stor):
   """Grow a stack of block devices.
 
   This function is called recursively, with the childrens being the
   """Grow a stack of block devices.
 
   This function is called recursively, with the childrens being the
@@ -2469,6 +2943,8 @@ def BlockdevGrow(disk, amount, dryrun, backingstore):
       only, or on "logical" storage only; e.g. DRBD is logical storage,
       whereas LVM, file, RBD are backing storage
   @rtype: (status, result)
       only, or on "logical" storage only; e.g. DRBD is logical storage,
       whereas LVM, file, RBD are backing storage
   @rtype: (status, result)
+  @type excl_stor: boolean
+  @param excl_stor: Whether exclusive_storage is active
   @return: a tuple with the status of the operation (True/False), and
       the errors message if status is False
 
   @return: a tuple with the status of the operation (True/False), and
       the errors message if status is False
 
@@ -2478,7 +2954,7 @@ def BlockdevGrow(disk, amount, dryrun, backingstore):
     _Fail("Cannot find block device %s", disk)
 
   try:
     _Fail("Cannot find block device %s", disk)
 
   try:
-    r_dev.Grow(amount, dryrun, backingstore)
+    r_dev.Grow(amount, dryrun, backingstore, excl_stor)
   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)
 
@@ -2495,12 +2971,12 @@ def BlockdevSnapshot(disk):
   @return: snapshot disk ID as (vg, lv)
 
   """
   @return: snapshot disk ID as (vg, lv)
 
   """
-  if disk.dev_type == constants.LD_DRBD8:
+  if disk.dev_type == constants.DT_DRBD8:
     if not disk.children:
       _Fail("DRBD device '%s' without backing storage cannot be snapshotted",
             disk.unique_id)
     return BlockdevSnapshot(disk.children[0])
     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:
+  elif disk.dev_type == constants.DT_PLAIN:
     r_dev = _RecursiveFindBD(disk)
     if r_dev is not None:
       # FIXME: choose a saner value for the snapshot size
     r_dev = _RecursiveFindBD(disk)
     if r_dev is not None:
       # FIXME: choose a saner value for the snapshot size
@@ -2513,6 +2989,32 @@ def BlockdevSnapshot(disk):
           disk.unique_id, disk.dev_type)
 
 
           disk.unique_id, disk.dev_type)
 
 
+def BlockdevSetInfo(disk, info):
+  """Sets 'metadata' information on block devices.
+
+  This function sets 'info' metadata on block devices. Initial
+  information is set at device creation; this function should be used
+  for example after renames.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk to be grown
+  @type info: string
+  @param info: new 'info' metadata
+  @rtype: (status, result)
+  @return: a tuple with the status of the operation (True/False), and
+      the errors message if status is False
+
+  """
+  r_dev = _RecursiveFindBD(disk)
+  if r_dev is None:
+    _Fail("Cannot find block device %s", disk)
+
+  try:
+    r_dev.SetInfo(info)
+  except errors.BlockDeviceError, err:
+    _Fail("Failed to set information on block device: %s", err, exc=True)
+
+
 def FinalizeExport(instance, snap_disks):
   """Write out the export configuration information.
 
 def FinalizeExport(instance, snap_disks):
   """Write out the export configuration information.
 
@@ -2559,6 +3061,8 @@ def FinalizeExport(instance, snap_disks):
     config.set(constants.INISECT_INS, "nic%d_mac" %
                nic_count, "%s" % nic.mac)
     config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip)
     config.set(constants.INISECT_INS, "nic%d_mac" %
                nic_count, "%s" % nic.mac)
     config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip)
+    config.set(constants.INISECT_INS, "nic%d_network" % nic_count,
+               "%s" % nic.network)
     for param in constants.NICS_PARAMETER_TYPES:
       config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
                  "%s" % nic.nicparams.get(param, None))
     for param in constants.NICS_PARAMETER_TYPES:
       config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param),
                  "%s" % nic.nicparams.get(param, None))
@@ -2572,7 +3076,7 @@ def FinalizeExport(instance, snap_disks):
       config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
                  ("%s" % disk.iv_name))
       config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
       config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count,
                  ("%s" % disk.iv_name))
       config.set(constants.INISECT_INS, "disk%d_dump" % disk_count,
-                 ("%s" % disk.physical_id[1]))
+                 ("%s" % disk.logical_id[1]))
       config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
                  ("%d" % disk.size))
 
       config.set(constants.INISECT_INS, "disk%d_size" % disk_count,
                  ("%d" % disk.size))
 
@@ -2655,11 +3159,9 @@ def BlockdevRename(devlist):
   """Rename a list of block devices.
 
   @type devlist: list of tuples
   """Rename a list of block devices.
 
   @type devlist: list of tuples
-  @param devlist: list of tuples of the form  (disk,
-      new_logical_id, new_physical_id); disk is an
-      L{objects.Disk} object describing the current disk,
-      and new logical_id/physical_id is the name we
-      rename it to
+  @param devlist: list of tuples of the form  (disk, new_unique_id); disk is
+      an L{objects.Disk} object describing the current disk, and new
+      unique_id is the name we rename it to
   @rtype: boolean
   @return: True if all renames succeeded, False otherwise
 
   @rtype: boolean
   @return: True if all renames succeeded, False otherwise
 
@@ -2705,19 +3207,9 @@ def _TransformFileStorageDir(fs_dir):
   @return: the normalized path if valid, None otherwise
 
   """
   @return: the normalized path if valid, None otherwise
 
   """
-  if not (constants.ENABLE_FILE_STORAGE or
-          constants.ENABLE_SHARED_FILE_STORAGE):
-    _Fail("File storage disabled at configure time")
-  cfg = _GetConfig()
-  fs_dir = os.path.normpath(fs_dir)
-  base_fstore = cfg.GetFileStorageDir()
-  base_shared = cfg.GetSharedFileStorageDir()
-  if not (utils.IsBelowDir(base_fstore, fs_dir) or
-          utils.IsBelowDir(base_shared, fs_dir)):
-    _Fail("File storage directory '%s' is not under base file"
-          " storage directory '%s' or shared storage directory '%s'",
-          fs_dir, base_fstore, base_shared)
-  return fs_dir
+  filestorage.CheckFileStoragePath(fs_dir)
+
+  return os.path.normpath(fs_dir)
 
 
 def CreateFileStorageDir(file_storage_dir):
 
 
 def CreateFileStorageDir(file_storage_dir):
@@ -2834,7 +3326,7 @@ def JobQueueUpdate(file_name, content):
 
   # Write and replace the file atomically
   utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid,
 
   # Write and replace the file atomically
   utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid,
-                  gid=getents.masterd_gid)
+                  gid=getents.daemons_gid, mode=constants.JOB_QUEUE_FILES_PERMS)
 
 
 def JobQueueRename(old, new):
 
 
 def JobQueueRename(old, new):
@@ -2858,8 +3350,8 @@ def JobQueueRename(old, new):
 
   getents = runtime.GetEnts()
 
 
   getents = runtime.GetEnts()
 
-  utils.RenameFile(old, new, mkdir=True, mkdir_mode=0700,
-                   dir_uid=getents.masterd_uid, dir_gid=getents.masterd_gid)
+  utils.RenameFile(old, new, mkdir=True, mkdir_mode=0750,
+                   dir_uid=getents.masterd_uid, dir_gid=getents.daemons_gid)
 
 
 def BlockdevClose(instance_name, disks):
 
 
 def BlockdevClose(instance_name, disks):
@@ -3140,8 +3632,6 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
 
     assert isinstance(disk_index, (int, long))
 
 
     assert isinstance(disk_index, (int, long))
 
-    real_disk = _OpenRealBD(disk)
-
     inst_os = OSFromDisk(instance.os)
     env = OSEnvironment(instance, inst_os)
 
     inst_os = OSFromDisk(instance.os)
     env = OSEnvironment(instance, inst_os)
 
@@ -3151,6 +3641,7 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs):
       script = inst_os.import_script
 
     elif mode == constants.IEM_EXPORT:
       script = inst_os.import_script
 
     elif mode == constants.IEM_EXPORT:
+      real_disk = _OpenRealBD(disk)
       env["EXPORT_DEVICE"] = real_disk.dev_path
       env["EXPORT_INDEX"] = str(disk_index)
       script = inst_os.export_script
       env["EXPORT_DEVICE"] = real_disk.dev_path
       env["EXPORT_INDEX"] = str(disk_index)
       script = inst_os.export_script
@@ -3378,30 +3869,31 @@ def CleanupImportExport(name):
   shutil.rmtree(status_dir, ignore_errors=True)
 
 
   shutil.rmtree(status_dir, ignore_errors=True)
 
 
-def _FindDisks(nodes_ip, disks):
-  """Sets the physical ID on disks and returns the block devices.
+def _FindDisks(disks):
+  """Finds attached L{BlockDev}s for the given disks.
 
 
-  """
-  # set the correct physical ID
-  my_name = netutils.Hostname.GetSysName()
-  for cf in disks:
-    cf.SetPhysicalID(my_name, nodes_ip)
+  @type disks: list of L{objects.Disk}
+  @param disks: the disk objects we need to find
 
 
+  @return: list of L{BlockDev} objects or C{None} if a given disk
+           was not found or was no attached.
+
+  """
   bdevs = []
 
   bdevs = []
 
-  for cf in disks:
-    rd = _RecursiveFindBD(cf)
+  for disk in disks:
+    rd = _RecursiveFindBD(disk)
     if rd is None:
     if rd is None:
-      _Fail("Can't find device %s", cf)
+      _Fail("Can't find device %s", disk)
     bdevs.append(rd)
   return bdevs
 
 
     bdevs.append(rd)
   return bdevs
 
 
-def DrbdDisconnectNet(nodes_ip, disks):
+def DrbdDisconnectNet(disks):
   """Disconnects the network on a list of drbd devices.
 
   """
   """Disconnects the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   # disconnect disks
   for rd in bdevs:
 
   # disconnect disks
   for rd in bdevs:
@@ -3412,11 +3904,11 @@ def DrbdDisconnectNet(nodes_ip, disks):
             err, exc=True)
 
 
             err, exc=True)
 
 
-def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
+def DrbdAttachNet(disks, instance_name, multimaster):
   """Attaches the network on a list of drbd devices.
 
   """
   """Attaches the network on a list of drbd devices.
 
   """
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
 
   if multimaster:
     for idx, rd in enumerate(bdevs):
@@ -3474,7 +3966,7 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster):
         _Fail("Can't change to primary mode: %s", err)
 
 
         _Fail("Can't change to primary mode: %s", err)
 
 
-def DrbdWaitSync(nodes_ip, disks):
+def DrbdWaitSync(disks):
   """Wait until DRBDs have synchronized.
 
   """
   """Wait until DRBDs have synchronized.
 
   """
@@ -3484,7 +3976,7 @@ def DrbdWaitSync(nodes_ip, disks):
       raise utils.RetryAgain()
     return stats
 
       raise utils.RetryAgain()
     return stats
 
-  bdevs = _FindDisks(nodes_ip, disks)
+  bdevs = _FindDisks(disks)
 
   min_resync = 100
   alldone = True
 
   min_resync = 100
   alldone = True
@@ -3504,17 +3996,36 @@ def DrbdWaitSync(nodes_ip, disks):
   return (alldone, min_resync)
 
 
   return (alldone, min_resync)
 
 
+def DrbdNeedsActivation(disks):
+  """Checks which of the passed disks needs activation and returns their UUIDs.
+
+  """
+  faulty_disks = []
+
+  for disk in disks:
+    rd = _RecursiveFindBD(disk)
+    if rd is None:
+      faulty_disks.append(disk)
+      continue
+
+    stats = rd.GetProcStatus()
+    if stats.is_standalone or stats.is_diskless:
+      faulty_disks.append(disk)
+
+  return [disk.uuid for disk in faulty_disks]
+
+
 def GetDrbdUsermodeHelper():
   """Returns DRBD usermode helper currently configured.
 
   """
   try:
 def GetDrbdUsermodeHelper():
   """Returns DRBD usermode helper currently configured.
 
   """
   try:
-    return bdev.BaseDRBD.GetUsermodeHelper()
+    return drbd.DRBD8.GetUsermodeHelper()
   except errors.BlockDeviceError, err:
     _Fail(str(err))
 
 
   except errors.BlockDeviceError, err:
     _Fail(str(err))
 
 
-def PowercycleNode(hypervisor_type):
+def PowercycleNode(hypervisor_type, hvparams=None):
   """Hard-powercycle the node.
 
   Because we need to return first, and schedule the powercycle in the
   """Hard-powercycle the node.
 
   Because we need to return first, and schedule the powercycle in the
@@ -3535,7 +4046,237 @@ def PowercycleNode(hypervisor_type):
   except Exception: # pylint: disable=W0703
     pass
   time.sleep(5)
   except Exception: # pylint: disable=W0703
     pass
   time.sleep(5)
-  hyper.PowercycleNode()
+  hyper.PowercycleNode(hvparams=hvparams)
+
+
+def _VerifyRestrictedCmdName(cmd):
+  """Verifies a restricted command name.
+
+  @type cmd: string
+  @param cmd: Command name
+  @rtype: tuple; (boolean, string or None)
+  @return: The tuple's first element is the status; if C{False}, the second
+    element is an error message string, otherwise it's C{None}
+
+  """
+  if not cmd.strip():
+    return (False, "Missing command name")
+
+  if os.path.basename(cmd) != cmd:
+    return (False, "Invalid command name")
+
+  if not constants.EXT_PLUGIN_MASK.match(cmd):
+    return (False, "Command name contains forbidden characters")
+
+  return (True, None)
+
+
+def _CommonRestrictedCmdCheck(path, owner):
+  """Common checks for restricted command file system directories and files.
+
+  @type path: string
+  @param path: Path to check
+  @param owner: C{None} or tuple containing UID and GID
+  @rtype: tuple; (boolean, string or C{os.stat} result)
+  @return: The tuple's first element is the status; if C{False}, the second
+    element is an error message string, otherwise it's the result of C{os.stat}
+
+  """
+  if owner is None:
+    # Default to root as owner
+    owner = (0, 0)
+
+  try:
+    st = os.stat(path)
+  except EnvironmentError, err:
+    return (False, "Can't stat(2) '%s': %s" % (path, err))
+
+  if stat.S_IMODE(st.st_mode) & (~_RCMD_MAX_MODE):
+    return (False, "Permissions on '%s' are too permissive" % path)
+
+  if (st.st_uid, st.st_gid) != owner:
+    (owner_uid, owner_gid) = owner
+    return (False, "'%s' is not owned by %s:%s" % (path, owner_uid, owner_gid))
+
+  return (True, st)
+
+
+def _VerifyRestrictedCmdDirectory(path, _owner=None):
+  """Verifies restricted command directory.
+
+  @type path: string
+  @param path: Path to check
+  @rtype: tuple; (boolean, string or None)
+  @return: The tuple's first element is the status; if C{False}, the second
+    element is an error message string, otherwise it's C{None}
+
+  """
+  (status, value) = _CommonRestrictedCmdCheck(path, _owner)
+
+  if not status:
+    return (False, value)
+
+  if not stat.S_ISDIR(value.st_mode):
+    return (False, "Path '%s' is not a directory" % path)
+
+  return (True, None)
+
+
+def _VerifyRestrictedCmd(path, cmd, _owner=None):
+  """Verifies a whole restricted command and returns its executable filename.
+
+  @type path: string
+  @param path: Directory containing restricted commands
+  @type cmd: string
+  @param cmd: Command name
+  @rtype: tuple; (boolean, string)
+  @return: The tuple's first element is the status; if C{False}, the second
+    element is an error message string, otherwise the second element is the
+    absolute path to the executable
+
+  """
+  executable = utils.PathJoin(path, cmd)
+
+  (status, msg) = _CommonRestrictedCmdCheck(executable, _owner)
+
+  if not status:
+    return (False, msg)
+
+  if not utils.IsExecutable(executable):
+    return (False, "access(2) thinks '%s' can't be executed" % executable)
+
+  return (True, executable)
+
+
+def _PrepareRestrictedCmd(path, cmd,
+                          _verify_dir=_VerifyRestrictedCmdDirectory,
+                          _verify_name=_VerifyRestrictedCmdName,
+                          _verify_cmd=_VerifyRestrictedCmd):
+  """Performs a number of tests on a restricted command.
+
+  @type path: string
+  @param path: Directory containing restricted commands
+  @type cmd: string
+  @param cmd: Command name
+  @return: Same as L{_VerifyRestrictedCmd}
+
+  """
+  # Verify the directory first
+  (status, msg) = _verify_dir(path)
+  if status:
+    # Check command if everything was alright
+    (status, msg) = _verify_name(cmd)
+
+  if not status:
+    return (False, msg)
+
+  # Check actual executable
+  return _verify_cmd(path, cmd)
+
+
+def RunRestrictedCmd(cmd,
+                     _lock_timeout=_RCMD_LOCK_TIMEOUT,
+                     _lock_file=pathutils.RESTRICTED_COMMANDS_LOCK_FILE,
+                     _path=pathutils.RESTRICTED_COMMANDS_DIR,
+                     _sleep_fn=time.sleep,
+                     _prepare_fn=_PrepareRestrictedCmd,
+                     _runcmd_fn=utils.RunCmd,
+                     _enabled=constants.ENABLE_RESTRICTED_COMMANDS):
+  """Executes a restricted command after performing strict tests.
+
+  @type cmd: string
+  @param cmd: Command name
+  @rtype: string
+  @return: Command output
+  @raise RPCFail: In case of an error
+
+  """
+  logging.info("Preparing to run restricted command '%s'", cmd)
+
+  if not _enabled:
+    _Fail("Restricted commands disabled at configure time")
+
+  lock = None
+  try:
+    cmdresult = None
+    try:
+      lock = utils.FileLock.Open(_lock_file)
+      lock.Exclusive(blocking=True, timeout=_lock_timeout)
+
+      (status, value) = _prepare_fn(_path, cmd)
+
+      if status:
+        cmdresult = _runcmd_fn([value], env={}, reset_env=True,
+                               postfork_fn=lambda _: lock.Unlock())
+      else:
+        logging.error(value)
+    except Exception: # pylint: disable=W0703
+      # Keep original error in log
+      logging.exception("Caught exception")
+
+    if cmdresult is None:
+      logging.info("Sleeping for %0.1f seconds before returning",
+                   _RCMD_INVALID_DELAY)
+      _sleep_fn(_RCMD_INVALID_DELAY)
+
+      # Do not include original error message in returned error
+      _Fail("Executing command '%s' failed" % cmd)
+    elif cmdresult.failed or cmdresult.fail_reason:
+      _Fail("Restricted command '%s' failed: %s; output: %s",
+            cmd, cmdresult.fail_reason, cmdresult.output)
+    else:
+      return cmdresult.output
+  finally:
+    if lock is not None:
+      # Release lock at last
+      lock.Close()
+      lock = None
+
+
+def SetWatcherPause(until, _filename=pathutils.WATCHER_PAUSEFILE):
+  """Creates or removes the watcher pause file.
+
+  @type until: None or number
+  @param until: Unix timestamp saying until when the watcher shouldn't run
+
+  """
+  if until is None:
+    logging.info("Received request to no longer pause watcher")
+    utils.RemoveFile(_filename)
+  else:
+    logging.info("Received request to pause watcher until %s", until)
+
+    if not ht.TNumber(until):
+      _Fail("Duration must be numeric")
+
+    utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644)
+
+
+def ConfigureOVS(ovs_name, ovs_link):
+  """Creates a OpenvSwitch on the node.
+
+  This function sets up a OpenvSwitch on the node with given name nad
+  connects it via a given eth device.
+
+  @type ovs_name: string
+  @param ovs_name: Name of the OpenvSwitch to create.
+  @type ovs_link: None or string
+  @param ovs_link: Ethernet device for outside connection (can be missing)
+
+  """
+  # Initialize the OpenvSwitch
+  result = utils.RunCmd(["ovs-vsctl", "add-br", ovs_name])
+  if result.failed:
+    _Fail("Failed to create openvswitch %s. Script return value: %s, output:"
+          " '%s'" % result.exit_code, result.output, log=True)
+
+  # And connect it to a physical interface, if given
+  if ovs_link:
+    result = utils.RunCmd(["ovs-vsctl", "add-port", ovs_name, ovs_link])
+    if result.failed:
+      _Fail("Failed to connect openvswitch to  interface %s. Script return"
+            " value: %s, output: '%s'" % ovs_link, result.exit_code,
+            result.output, log=True)
 
 
 class HooksRunner(object):
 
 
 class HooksRunner(object):