Merge branch 'devel-2.0' into devel-2.1
authorIustin Pop <iustin@google.com>
Mon, 28 Dec 2009 12:05:40 +0000 (13:05 +0100)
committerIustin Pop <iustin@google.com>
Mon, 28 Dec 2009 12:05:40 +0000 (13:05 +0100)
* devel-2.0:
  Fix indentation in hv_kvm
  Implement BuildHooksEnv for NoHooksLU
  Clarifiy some more wide pylint disables
  Fix two bugs in seldom-used codepaths
  Update pylintrc
  Add targetted pylint disables
  Partial cherry-pick of 6c881c5 from the 2.1 branch
  Add a release script
  Fix a typo in the doc string

Conflicts:
lib/cli.py
lib/cmdlib.py
lib/hypervisor/hv_kvm.py
lib/jstore.py
lib/locking.py
lib/mcpu.py
lib/rapi/rlib2.py

Many of the conflicts were on code removed from 2.1, so the resolving was
trivial.

15 files changed:
1  2 
lib/backend.py
lib/bdev.py
lib/cli.py
lib/cmdlib.py
lib/errors.py
lib/http/client.py
lib/hypervisor/hv_fake.py
lib/locking.py
lib/objects.py
lib/rapi/baserlib.py
lib/rapi/connector.py
lib/rapi/rlib2.py
lib/rpc.py
lib/serializer.py
pylintrc

diff --cc lib/backend.py
@@@ -1206,9 -1151,12 +1206,10 @@@ def BlockdevCreate(disk, size, owner, o
          # we need the children open in case the device itself has to
          # be assembled
          try:
+           # pylint: disable-msg=E1103
            crdev.Open()
          except errors.BlockDeviceError, err:
 -          errmsg = "Can't make child '%s' read-write: %s" % (child, err)
 -          logging.error(errmsg)
 -          return False, errmsg
 +          _Fail("Can't make child '%s' read-write: %s", child, err)
        clist.append(crdev)
  
    try:
@@@ -1340,11 -1296,14 +1341,12 @@@ def BlockdevAssemble(disk, owner, as_pr
    try:
      result = _RecursiveAssembleBD(disk, owner, as_primary)
      if isinstance(result, bdev.BlockDev):
+       # pylint: disable-msg=E1103
        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):
diff --cc lib/bdev.py
Simple merge
diff --cc lib/cli.py
Simple merge
diff --cc lib/cmdlib.py
@@@ -87,14 -87,8 +90,14 @@@ class LogicalUnit(object)
      self.recalculate_locks = {}
      self.__ssh = None
      # logging
-     self.LogWarning = processor.LogWarning
-     self.LogInfo = processor.LogInfo
-     self.LogStep = processor.LogStep
+     self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103
+     self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103
++    self.LogStep = processor.LogStep # pylint: disable-msg=C0103
 +    # support for dry-run
 +    self.dry_run_result = None
 +
 +    # Tasklets
 +    self.tasklets = None
  
      for attr_name in self._OP_REQP:
        attr_val = getattr(op, attr_name, None)
@@@ -357,53 -335,15 +360,61 @@@ class NoHooksLU(LogicalUnit): # pylint
    HPATH = None
    HTYPE = None
  
+   def BuildHooksEnv(self):
+     """Empty BuildHooksEnv for NoHooksLu.
+     This just raises an error.
+     """
+     assert False, "BuildHooksEnv called for NoHooksLUs"
  
 +class Tasklet:
 +  """Tasklet base class.
 +
 +  Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
 +  they can mix legacy code with tasklets. Locking needs to be done in the LU,
 +  tasklets know nothing about locks.
 +
 +  Subclasses must follow these rules:
 +    - Implement CheckPrereq
 +    - Implement Exec
 +
 +  """
 +  def __init__(self, lu):
 +    self.lu = lu
 +
 +    # Shortcuts
 +    self.cfg = lu.cfg
 +    self.rpc = lu.rpc
 +
 +  def CheckPrereq(self):
 +    """Check prerequisites for this tasklets.
 +
 +    This method should check whether the prerequisites for the execution of
 +    this tasklet are fulfilled. It can do internode communication, but it
 +    should be idempotent - no cluster or system changes are allowed.
 +
 +    The method should raise errors.OpPrereqError in case something is not
 +    fulfilled. Its return value is ignored.
 +
 +    This method should also update all parameters to their canonical form if it
 +    hasn't been done before.
 +
 +    """
 +    raise NotImplementedError
 +
 +  def Exec(self, feedback_fn):
 +    """Execute the tasklet.
 +
 +    This method should implement the actual work. It should raise
 +    errors.OpExecError for failures that are somewhat dealt with in code, or
 +    expected.
 +
 +    """
 +    raise NotImplementedError
 +
 +
  def _GetWantedNodes(lu, nodes):
    """Returns list of checked and expanded node names.
  
@@@ -6350,468 -5211,114 +6361,469 @@@ class LUReplaceDisks(LogicalUnit)
          self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
        self._LockInstancesNodes()
  
 -  def _RunAllocator(self):
 -    """Compute a new secondary node using an IAllocator.
 +  def BuildHooksEnv(self):
 +    """Build hooks env.
 +
 +    This runs on the master, the primary and all the secondaries.
 +
 +    """
 +    instance = self.replacer.instance
 +    env = {
 +      "MODE": self.op.mode,
 +      "NEW_SECONDARY": self.op.remote_node,
 +      "OLD_SECONDARY": instance.secondary_nodes[0],
 +      }
 +    env.update(_BuildInstanceHookEnvByObject(self, instance))
 +    nl = [
 +      self.cfg.GetMasterNode(),
 +      instance.primary_node,
 +      ]
 +    if self.op.remote_node is not None:
 +      nl.append(self.op.remote_node)
 +    return env, nl, nl
 +
 +
 +class LUEvacuateNode(LogicalUnit):
 +  """Relocate the secondary instances from a node.
 +
 +  """
 +  HPATH = "node-evacuate"
 +  HTYPE = constants.HTYPE_NODE
 +  _OP_REQP = ["node_name"]
 +  REQ_BGL = False
 +
 +  def CheckArguments(self):
 +    if not hasattr(self.op, "remote_node"):
 +      self.op.remote_node = None
 +    if not hasattr(self.op, "iallocator"):
 +      self.op.iallocator = None
 +
 +    TLReplaceDisks.CheckArguments(constants.REPLACE_DISK_CHG,
 +                                  self.op.remote_node,
 +                                  self.op.iallocator)
 +
 +  def ExpandNames(self):
 +    self.op.node_name = self.cfg.ExpandNodeName(self.op.node_name)
 +    if self.op.node_name is None:
 +      raise errors.OpPrereqError("Node '%s' not known" % self.op.node_name,
 +                                 errors.ECODE_NOENT)
 +
 +    self.needed_locks = {}
 +
 +    # Declare node locks
 +    if self.op.iallocator is not None:
 +      self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
 +
 +    elif self.op.remote_node is not None:
 +      remote_node = self.cfg.ExpandNodeName(self.op.remote_node)
 +      if remote_node is None:
 +        raise errors.OpPrereqError("Node '%s' not known" %
 +                                   self.op.remote_node, errors.ECODE_NOENT)
 +
 +      self.op.remote_node = remote_node
 +
 +      # Warning: do not remove the locking of the new secondary here
 +      # unless DRBD8.AddChildren is changed to work in parallel;
 +      # currently it doesn't since parallel invocations of
 +      # FindUnusedMinor will conflict
 +      self.needed_locks[locking.LEVEL_NODE] = [remote_node]
 +      self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
 +
 +    else:
 +      raise errors.OpPrereqError("Invalid parameters", errors.ECODE_INVAL)
 +
 +    # Create tasklets for replacing disks for all secondary instances on this
 +    # node
 +    names = []
 +    tasklets = []
 +
 +    for inst in _GetNodeSecondaryInstances(self.cfg, self.op.node_name):
 +      logging.debug("Replacing disks for instance %s", inst.name)
 +      names.append(inst.name)
 +
 +      replacer = TLReplaceDisks(self, inst.name, constants.REPLACE_DISK_CHG,
 +                                self.op.iallocator, self.op.remote_node, [])
 +      tasklets.append(replacer)
 +
 +    self.tasklets = tasklets
 +    self.instance_names = names
 +
 +    # Declare instance locks
 +    self.needed_locks[locking.LEVEL_INSTANCE] = self.instance_names
 +
 +  def DeclareLocks(self, level):
 +    # If we're not already locking all nodes in the set we have to declare the
 +    # instance's primary/secondary nodes.
 +    if (level == locking.LEVEL_NODE and
 +        self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
 +      self._LockInstancesNodes()
 +
 +  def BuildHooksEnv(self):
 +    """Build hooks env.
 +
 +    This runs on the master, the primary and all the secondaries.
 +
 +    """
 +    env = {
 +      "NODE_NAME": self.op.node_name,
 +      }
 +
 +    nl = [self.cfg.GetMasterNode()]
 +
 +    if self.op.remote_node is not None:
 +      env["NEW_SECONDARY"] = self.op.remote_node
 +      nl.append(self.op.remote_node)
 +
 +    return (env, nl, nl)
 +
 +
 +class TLReplaceDisks(Tasklet):
 +  """Replaces disks for an instance.
 +
 +  Note: Locking is not within the scope of this class.
 +
 +  """
 +  def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
 +               disks):
 +    """Initializes this class.
 +
 +    """
 +    Tasklet.__init__(self, lu)
 +
 +    # Parameters
 +    self.instance_name = instance_name
 +    self.mode = mode
 +    self.iallocator_name = iallocator_name
 +    self.remote_node = remote_node
 +    self.disks = disks
 +
 +    # Runtime data
 +    self.instance = None
 +    self.new_node = None
 +    self.target_node = None
 +    self.other_node = None
 +    self.remote_node_info = None
 +    self.node_secondary_ip = None
 +
 +  @staticmethod
 +  def CheckArguments(mode, remote_node, iallocator):
 +    """Helper function for users of this class.
 +
 +    """
 +    # check for valid parameter combination
 +    if mode == constants.REPLACE_DISK_CHG:
 +      if remote_node is None and iallocator is None:
 +        raise errors.OpPrereqError("When changing the secondary either an"
 +                                   " iallocator script must be used or the"
 +                                   " new node given", errors.ECODE_INVAL)
 +
 +      if remote_node is not None and iallocator is not None:
 +        raise errors.OpPrereqError("Give either the iallocator or the new"
 +                                   " secondary, not both", errors.ECODE_INVAL)
 +
 +    elif remote_node is not None or iallocator is not None:
 +      # Not replacing the secondary
 +      raise errors.OpPrereqError("The iallocator and new node options can"
 +                                 " only be used when changing the"
 +                                 " secondary node", errors.ECODE_INVAL)
 +
 +  @staticmethod
 +  def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
 +    """Compute a new secondary node using an IAllocator.
 +
 +    """
 +    ial = IAllocator(lu.cfg, lu.rpc,
 +                     mode=constants.IALLOCATOR_MODE_RELOC,
 +                     name=instance_name,
 +                     relocate_from=relocate_from)
 +
 +    ial.Run(iallocator_name)
 +
 +    if not ial.success:
 +      raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
 +                                 " %s" % (iallocator_name, ial.info),
 +                                 errors.ECODE_NORES)
 +
 +    if len(ial.nodes) != ial.required_nodes:
 +      raise errors.OpPrereqError("iallocator '%s' returned invalid number"
 +                                 " of nodes (%s), required %s" %
-                                  (len(ial.nodes), ial.required_nodes),
++                                 (iallocator_name,
++                                  len(ial.nodes), ial.required_nodes),
 +                                 errors.ECODE_FAULT)
 +
 +    remote_node_name = ial.nodes[0]
 +
 +    lu.LogInfo("Selected new secondary for instance '%s': %s",
 +               instance_name, remote_node_name)
 +
 +    return remote_node_name
 +
 +  def _FindFaultyDisks(self, node_name):
 +    return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
 +                                    node_name, True)
 +
 +  def CheckPrereq(self):
 +    """Check prerequisites.
 +
 +    This checks that the instance is in the cluster.
 +
 +    """
 +    self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
 +    assert instance is not None, \
 +      "Cannot retrieve locked instance %s" % self.instance_name
 +
 +    if instance.disk_template != constants.DT_DRBD8:
 +      raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
 +                                 " instances", errors.ECODE_INVAL)
 +
 +    if len(instance.secondary_nodes) != 1:
 +      raise errors.OpPrereqError("The instance has a strange layout,"
 +                                 " expected one secondary but found %d" %
 +                                 len(instance.secondary_nodes),
 +                                 errors.ECODE_FAULT)
 +
 +    secondary_node = instance.secondary_nodes[0]
 +
 +    if self.iallocator_name is None:
 +      remote_node = self.remote_node
 +    else:
 +      remote_node = self._RunAllocator(self.lu, self.iallocator_name,
 +                                       instance.name, instance.secondary_nodes)
 +
 +    if remote_node is not None:
 +      self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
 +      assert self.remote_node_info is not None, \
 +        "Cannot retrieve locked node %s" % remote_node
 +    else:
 +      self.remote_node_info = None
 +
 +    if remote_node == self.instance.primary_node:
 +      raise errors.OpPrereqError("The specified node is the primary node of"
 +                                 " the instance.", errors.ECODE_INVAL)
 +
 +    if remote_node == secondary_node:
 +      raise errors.OpPrereqError("The specified node is already the"
 +                                 " secondary node of the instance.",
 +                                 errors.ECODE_INVAL)
 +
 +    if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
 +                                    constants.REPLACE_DISK_CHG):
 +      raise errors.OpPrereqError("Cannot specify disks to be replaced",
 +                                 errors.ECODE_INVAL)
 +
 +    if self.mode == constants.REPLACE_DISK_AUTO:
 +      faulty_primary = self._FindFaultyDisks(instance.primary_node)
 +      faulty_secondary = self._FindFaultyDisks(secondary_node)
 +
 +      if faulty_primary and faulty_secondary:
 +        raise errors.OpPrereqError("Instance %s has faulty disks on more than"
 +                                   " one node and can not be repaired"
 +                                   " automatically" % self.instance_name,
 +                                   errors.ECODE_STATE)
 +
 +      if faulty_primary:
 +        self.disks = faulty_primary
 +        self.target_node = instance.primary_node
 +        self.other_node = secondary_node
 +        check_nodes = [self.target_node, self.other_node]
 +      elif faulty_secondary:
 +        self.disks = faulty_secondary
 +        self.target_node = secondary_node
 +        self.other_node = instance.primary_node
 +        check_nodes = [self.target_node, self.other_node]
 +      else:
 +        self.disks = []
 +        check_nodes = []
 +
 +    else:
 +      # Non-automatic modes
 +      if self.mode == constants.REPLACE_DISK_PRI:
 +        self.target_node = instance.primary_node
 +        self.other_node = secondary_node
 +        check_nodes = [self.target_node, self.other_node]
 +
 +      elif self.mode == constants.REPLACE_DISK_SEC:
 +        self.target_node = secondary_node
 +        self.other_node = instance.primary_node
 +        check_nodes = [self.target_node, self.other_node]
 +
 +      elif self.mode == constants.REPLACE_DISK_CHG:
 +        self.new_node = remote_node
 +        self.other_node = instance.primary_node
 +        self.target_node = secondary_node
 +        check_nodes = [self.new_node, self.other_node]
 +
 +        _CheckNodeNotDrained(self.lu, remote_node)
 +
 +      else:
 +        raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
 +                                     self.mode)
 +
 +      # If not specified all disks should be replaced
 +      if not self.disks:
 +        self.disks = range(len(self.instance.disks))
 +
 +    for node in check_nodes:
 +      _CheckNodeOnline(self.lu, node)
 +
 +    # Check whether disks are valid
 +    for disk_idx in self.disks:
 +      instance.FindDisk(disk_idx)
 +
 +    # Get secondary node IP addresses
 +    node_2nd_ip = {}
 +
 +    for node_name in [self.target_node, self.other_node, self.new_node]:
 +      if node_name is not None:
 +        node_2nd_ip[node_name] = self.cfg.GetNodeInfo(node_name).secondary_ip
 +
 +    self.node_secondary_ip = node_2nd_ip
 +
 +  def Exec(self, feedback_fn):
 +    """Execute disk replacement.
 +
 +    This dispatches the disk replacement to the appropriate handler.
 +
 +    """
 +    if not self.disks:
 +      feedback_fn("No disks need replacement")
 +      return
 +
 +    feedback_fn("Replacing disk(s) %s for %s" %
 +                (utils.CommaJoin(self.disks), self.instance.name))
 +
 +    activate_disks = (not self.instance.admin_up)
 +
 +    # Activate the instance disks if we're replacing them on a down instance
 +    if activate_disks:
 +      _StartInstanceDisks(self.lu, self.instance, True)
 +
 +    try:
 +      # Should we replace the secondary node?
 +      if self.new_node is not None:
 +        fn = self._ExecDrbd8Secondary
 +      else:
 +        fn = self._ExecDrbd8DiskOnly
 +
 +      return fn(feedback_fn)
 +
 +    finally:
 +      # Deactivate the instance disks if we're replacing them on a
 +      # down instance
 +      if activate_disks:
 +        _SafeShutdownInstanceDisks(self.lu, self.instance)
 +
 +  def _CheckVolumeGroup(self, nodes):
 +    self.lu.LogInfo("Checking volume groups")
 +
 +    vgname = self.cfg.GetVGName()
 +
 +    # Make sure volume group exists on all involved nodes
 +    results = self.rpc.call_vg_list(nodes)
 +    if not results:
 +      raise errors.OpExecError("Can't list volume groups on the nodes")
 +
 +    for node in nodes:
 +      res = results[node]
 +      res.Raise("Error checking node %s" % node)
 +      if vgname not in res.payload:
 +        raise errors.OpExecError("Volume group '%s' not found on node %s" %
 +                                 (vgname, node))
 +
 +  def _CheckDisksExistence(self, nodes):
 +    # Check disk existence
 +    for idx, dev in enumerate(self.instance.disks):
 +      if idx not in self.disks:
 +        continue
 +
 +      for node in nodes:
 +        self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
 +        self.cfg.SetDiskID(dev, node)
 +
 +        result = self.rpc.call_blockdev_find(node, dev)
 +
 +        msg = result.fail_msg
 +        if msg or not result.payload:
 +          if not msg:
 +            msg = "disk not found"
 +          raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
 +                                   (idx, node, msg))
  
 -    """
 -    ial = IAllocator(self,
 -                     mode=constants.IALLOCATOR_MODE_RELOC,
 -                     name=self.op.instance_name,
 -                     relocate_from=[self.sec_node])
 +  def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
 +    for idx, dev in enumerate(self.instance.disks):
 +      if idx not in self.disks:
 +        continue
  
 -    ial.Run(self.op.iallocator)
 +      self.lu.LogInfo("Checking disk/%d consistency on node %s" %
 +                      (idx, node_name))
  
 -    if not ial.success:
 -      raise errors.OpPrereqError("Can't compute nodes using"
 -                                 " iallocator '%s': %s" % (self.op.iallocator,
 -                                                           ial.info))
 -    if len(ial.nodes) != ial.required_nodes:
 -      raise errors.OpPrereqError("iallocator '%s' returned invalid number"
 -                                 " of nodes (%s), required %s" %
 -                                 (self.op.iallocator,
 -                                  len(ial.nodes), ial.required_nodes))
 -    self.op.remote_node = ial.nodes[0]
 -    self.LogInfo("Selected new secondary for the instance: %s",
 -                 self.op.remote_node)
 +      if not _CheckDiskConsistency(self.lu, dev, node_name, on_primary,
 +                                   ldisk=ldisk):
 +        raise errors.OpExecError("Node %s has degraded storage, unsafe to"
 +                                 " replace disks for instance %s" %
 +                                 (node_name, self.instance.name))
  
 -  def BuildHooksEnv(self):
 -    """Build hooks env.
 +  def _CreateNewStorage(self, node_name):
 +    vgname = self.cfg.GetVGName()
 +    iv_names = {}
  
 -    This runs on the master, the primary and all the secondaries.
 +    for idx, dev in enumerate(self.instance.disks):
 +      if idx not in self.disks:
 +        continue
  
 -    """
 -    env = {
 -      "MODE": self.op.mode,
 -      "NEW_SECONDARY": self.op.remote_node,
 -      "OLD_SECONDARY": self.instance.secondary_nodes[0],
 -      }
 -    env.update(_BuildInstanceHookEnvByObject(self, self.instance))
 -    nl = [
 -      self.cfg.GetMasterNode(),
 -      self.instance.primary_node,
 -      ]
 -    if self.op.remote_node is not None:
 -      nl.append(self.op.remote_node)
 -    return env, nl, nl
 +      self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
  
 -  def CheckPrereq(self):
 -    """Check prerequisites.
 +      self.cfg.SetDiskID(dev, node_name)
  
 -    This checks that the instance is in the cluster.
 +      lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
 +      names = _GenerateUniqueNames(self.lu, lv_names)
  
 -    """
 -    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
 -    assert instance is not None, \
 -      "Cannot retrieve locked instance %s" % self.op.instance_name
 -    self.instance = instance
 +      lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
 +                             logical_id=(vgname, names[0]))
 +      lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
 +                             logical_id=(vgname, names[1]))
  
 -    if instance.disk_template != constants.DT_DRBD8:
 -      raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
 -                                 " instances")
 +      new_lvs = [lv_data, lv_meta]
 +      old_lvs = dev.children
 +      iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
  
 -    if len(instance.secondary_nodes) != 1:
 -      raise errors.OpPrereqError("The instance has a strange layout,"
 -                                 " expected one secondary but found %d" %
 -                                 len(instance.secondary_nodes))
 +      # we pass force_create=True to force the LVM creation
 +      for new_lv in new_lvs:
 +        _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
 +                        _GetInstanceInfoText(self.instance), False)
  
 -    self.sec_node = instance.secondary_nodes[0]
 +    return iv_names
  
 -    if self.op.iallocator is not None:
 -      self._RunAllocator()
 +  def _CheckDevices(self, node_name, iv_names):
 +    for name, (dev, old_lvs, new_lvs) in iv_names.iteritems():
 +      self.cfg.SetDiskID(dev, node_name)
  
 -    remote_node = self.op.remote_node
 -    if remote_node is not None:
 -      self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
 -      assert self.remote_node_info is not None, \
 -        "Cannot retrieve locked node %s" % remote_node
 -    else:
 -      self.remote_node_info = None
 -    if remote_node == instance.primary_node:
 -      raise errors.OpPrereqError("The specified node is the primary node of"
 -                                 " the instance.")
 -    elif remote_node == self.sec_node:
 -      raise errors.OpPrereqError("The specified node is already the"
 -                                 " secondary node of the instance.")
 -
 -    if self.op.mode == constants.REPLACE_DISK_PRI:
 -      n1 = self.tgt_node = instance.primary_node
 -      n2 = self.oth_node = self.sec_node
 -    elif self.op.mode == constants.REPLACE_DISK_SEC:
 -      n1 = self.tgt_node = self.sec_node
 -      n2 = self.oth_node = instance.primary_node
 -    elif self.op.mode == constants.REPLACE_DISK_CHG:
 -      n1 = self.new_node = remote_node
 -      n2 = self.oth_node = instance.primary_node
 -      self.tgt_node = self.sec_node
 -      _CheckNodeNotDrained(self, remote_node)
 -    else:
 -      raise errors.ProgrammerError("Unhandled disk replace mode")
 +      result = self.rpc.call_blockdev_find(node_name, dev)
  
 -    _CheckNodeOnline(self, n1)
 -    _CheckNodeOnline(self, n2)
 +      msg = result.fail_msg
 +      if msg or not result.payload:
 +        if not msg:
 +          msg = "disk not found"
 +        raise errors.OpExecError("Can't find DRBD device %s: %s" %
 +                                 (name, msg))
  
 -    if not self.op.disks:
 -      self.op.disks = range(len(instance.disks))
 +      if result.payload.is_degraded:
 +        raise errors.OpExecError("DRBD device %s is degraded!" % name)
  
 -    for disk_idx in self.op.disks:
 -      instance.FindDisk(disk_idx)
 +  def _RemoveOldStorage(self, node_name, iv_names):
 +    for name, (dev, old_lvs, _) in iv_names.iteritems():
 +      self.lu.LogInfo("Remove logical volumes for %s" % name)
 +
 +      for lv in old_lvs:
 +        self.cfg.SetDiskID(lv, node_name)
 +
 +        msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
 +        if msg:
 +          self.lu.LogWarning("Can't remove old LV: %s" % msg,
 +                             hint="remove unused LVs manually")
  
 -  def _ExecD8DiskOnly(self, feedback_fn):
 -    """Replace a disk on the primary or secondary for dbrd8.
 +  def _ExecDrbd8DiskOnly(self, feedback_fn):
 +    """Replace a disk on the primary or secondary for DRBD 8.
  
      The algorithm for replace is quite complicated:
  
diff --cc lib/errors.py
Simple merge
Simple merge
Simple merge
diff --cc lib/locking.py
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  # 02110-1301, USA.
  
- # Disable "Invalid name ..." message
- # pylint: disable-msg=C0103
  """Module implementing the Ganeti locking code."""
  
+ # pylint: disable-msg=W0212
+ # W0212 since e.g. LockSet methods use (a lot) the internals of
+ # SharedLock
 +import os
 +import select
  import threading
 -# Wouldn't it be better to define LockingError in the locking module?
 -# Well, for now that's how the rest of the code does it...
 +import time
 +import errno
 +
  from ganeti import errors
  from ganeti import utils
  
diff --cc lib/objects.py
Simple merge
Simple merge
Simple merge
Simple merge
diff --cc lib/rpc.py
Simple merge
Simple merge
diff --cc pylintrc
Simple merge