Revision a9e0c397 lib/cmdlib.py

b/lib/cmdlib.py
3336 3336
  """
3337 3337
  HPATH = "mirrors-replace"
3338 3338
  HTYPE = constants.HTYPE_INSTANCE
3339
  _OP_REQP = ["instance_name"]
3339
  _OP_REQP = ["instance_name", "mode", "disks"]
3340 3340

  
3341 3341
  def BuildHooksEnv(self):
3342 3342
    """Build hooks env.
......
3345 3345

  
3346 3346
    """
3347 3347
    env = {
3348
      "MODE": self.op.mode,
3348 3349
      "NEW_SECONDARY": self.op.remote_node,
3349 3350
      "OLD_SECONDARY": self.instance.secondary_nodes[0],
3350 3351
      }
......
3366 3367
                                 self.op.instance_name)
3367 3368
    self.instance = instance
3368 3369

  
3369
    if instance.disk_template != constants.DT_REMOTE_RAID1:
3370
    if instance.disk_template not in constants.DTS_NET_MIRROR:
3370 3371
      raise errors.OpPrereqError("Instance's disk layout is not"
3371
                                 " remote_raid1.")
3372
                                 " network mirrored.")
3372 3373

  
3373 3374
    if len(instance.secondary_nodes) != 1:
3374 3375
      raise errors.OpPrereqError("The instance has a strange layout,"
3375 3376
                                 " expected one secondary but found %d" %
3376 3377
                                 len(instance.secondary_nodes))
3377 3378

  
3379
    self.sec_node = instance.secondary_nodes[0]
3380

  
3378 3381
    remote_node = getattr(self.op, "remote_node", None)
3379
    if remote_node is None:
3380
      remote_node = instance.secondary_nodes[0]
3381
    else:
3382
    if remote_node is not None:
3382 3383
      remote_node = self.cfg.ExpandNodeName(remote_node)
3383 3384
      if remote_node is None:
3384 3385
        raise errors.OpPrereqError("Node '%s' not known" %
3385 3386
                                   self.op.remote_node)
3387
      self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
3388
    else:
3389
      self.remote_node_info = None
3386 3390
    if remote_node == instance.primary_node:
3387 3391
      raise errors.OpPrereqError("The specified node is the primary node of"
3388 3392
                                 " the instance.")
3393
    elif remote_node == self.sec_node:
3394
      # the user gave the current secondary, switch to
3395
      # 'no-replace-secondary' mode
3396
      remote_node = None
3397
    if (instance.disk_template == constants.DT_REMOTE_RAID1 and
3398
        self.op.mode != constants.REPLACE_DISK_ALL):
3399
      raise errors.OpPrereqError("Template 'remote_raid1' only allows all"
3400
                                 " disks replacement, not individual ones")
3401
    if instance.disk_template == constants.DT_DRBD8:
3402
      if self.op.mode == constants.REPLACE_DISK_ALL:
3403
        raise errors.OpPrereqError("Template 'drbd8' only allows primary or"
3404
                                   " secondary disk replacement, not"
3405
                                   " both at once")
3406
      elif self.op.mode == constants.REPLACE_DISK_PRI:
3407
        if remote_node is not None:
3408
          raise errors.OpPrereqError("Template 'drbd8' does not allow changing"
3409
                                     " the secondary while doing a primary"
3410
                                     " node disk replacement")
3411
        self.tgt_node = instance.primary_node
3412
      elif self.op.mode == constants.REPLACE_DISK_SEC:
3413
        self.new_node = remote_node # this can be None, in which case
3414
                                    # we don't change the secondary
3415
        self.tgt_node = instance.secondary_nodes[0]
3416
      else:
3417
        raise errors.ProgrammerError("Unhandled disk replace mode")
3418

  
3419
    for name in self.op.disks:
3420
      if instance.FindDisk(name) is None:
3421
        raise errors.OpPrereqError("Disk '%s' not found for instance '%s'" %
3422
                                   (name, instance.name))
3389 3423
    self.op.remote_node = remote_node
3390 3424

  
3391
  def Exec(self, feedback_fn):
3425
  def _ExecRR1(self, feedback_fn):
3392 3426
    """Replace the disks of an instance.
3393 3427

  
3394 3428
    """
3395 3429
    instance = self.instance
3396 3430
    iv_names = {}
3397 3431
    # start of work
3398
    remote_node = self.op.remote_node
3432
    if self.op.remote_node is None:
3433
      remote_node = self.sec_node
3434
    else:
3435
      remote_node = self.op.remote_node
3399 3436
    cfg = self.cfg
3400 3437
    for dev in instance.disks:
3401 3438
      size = dev.size
......
3479 3516

  
3480 3517
      cfg.AddInstance(instance)
3481 3518

  
3519
  def _ExecD8DiskOnly(self, feedback_fn):
3520
    """Replace a disk on the primary or secondary for dbrd8.
3521

  
3522
    The algorithm for replace is quite complicated:
3523
      - for each disk to be replaced:
3524
        - create new LVs on the target node with unique names
3525
        - detach old LVs from the drbd device
3526
        - rename old LVs to name_replaced.<time_t>
3527
        - rename new LVs to old LVs
3528
        - attach the new LVs (with the old names now) to the drbd device
3529
      - wait for sync across all devices
3530
      - for each modified disk:
3531
        - remove old LVs (which have the name name_replaces.<time_t>)
3532

  
3533
    Failures are not very well handled.
3534
    """
3535
    instance = self.instance
3536
    iv_names = {}
3537
    vgname = self.cfg.GetVGName()
3538
    # start of work
3539
    cfg = self.cfg
3540
    tgt_node = self.tgt_node
3541
    for dev in instance.disks:
3542
      if not dev.iv_name in self.op.disks:
3543
        continue
3544
      size = dev.size
3545
      cfg.SetDiskID(dev, tgt_node)
3546
      lv_names = [".%s_%s" % (dev.iv_name, suf) for suf in ["data", "meta"]]
3547
      names = _GenerateUniqueNames(cfg, lv_names)
3548
      lv_data = objects.Disk(dev_type=constants.LD_LV, size=size,
3549
                             logical_id=(vgname, names[0]))
3550
      lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
3551
                             logical_id=(vgname, names[1]))
3552
      new_lvs = [lv_data, lv_meta]
3553
      old_lvs = dev.children
3554
      iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
3555
      logger.Info("adding new local storage on %s for %s" %
3556
                  (tgt_node, dev.iv_name))
3557
      # since we *always* want to create this LV, we use the
3558
      # _Create...OnPrimary (which forces the creation), even if we
3559
      # are talking about the secondary node
3560
      for new_lv in new_lvs:
3561
        if not _CreateBlockDevOnPrimary(cfg, tgt_node, new_lv,
3562
                                        _GetInstanceInfoText(instance)):
3563
          raise errors.OpExecError("Failed to create new LV named '%s' on"
3564
                                   " node '%s'" %
3565
                                   (new_lv.logical_id[1], tgt_node))
3566

  
3567
      if not rpc.call_blockdev_removechildren(tgt_node, dev, old_lvs):
3568
        raise errors.OpExecError("Can't detach drbd from local storage on node"
3569
                                 " %s for device %s" % (tgt_node, dev.iv_name))
3570
      dev.children = []
3571
      cfg.Update(instance)
3572

  
3573
      # ok, we created the new LVs, so now we know we have the needed
3574
      # storage; as such, we proceed on the target node to rename
3575
      # old_lv to _old, and new_lv to old_lv; note that we rename LVs
3576
      # using the assumption than logical_id == physical_id (which in
3577
      # turn is the unique_id on that node)
3578
      temp_suffix = int(time.time())
3579
      logger.Info("renaming the old LVs on the target node")
3580
      ren_fn = lambda d, suff: (d.physical_id[0],
3581
                                d.physical_id[1] + "_replaced-%s" % suff)
3582
      rlist = [(disk, ren_fn(disk, temp_suffix)) for disk in old_lvs]
3583
      if not rpc.call_blockdev_rename(tgt_node, rlist):
3584
        logger.Error("Can't rename old LVs on node %s" % tgt_node)
3585
        do_change_old = False
3586
      else:
3587
        do_change_old = True
3588
      # now we rename the new LVs to the old LVs
3589
      logger.Info("renaming the new LVs on the target node")
3590
      rlist = [(new, old.physical_id) for old, new in zip(old_lvs, new_lvs)]
3591
      if not rpc.call_blockdev_rename(tgt_node, rlist):
3592
        logger.Error("Can't rename new LVs on node %s" % tgt_node)
3593
      else:
3594
        for old, new in zip(old_lvs, new_lvs):
3595
          new.logical_id = old.logical_id
3596
          cfg.SetDiskID(new, tgt_node)
3597

  
3598
      if do_change_old:
3599
        for disk in old_lvs:
3600
          disk.logical_id = ren_fn(disk, temp_suffix)
3601
          cfg.SetDiskID(disk, tgt_node)
3602

  
3603
      # now that the new lvs have the old name, we can add them to the device
3604
      logger.Info("adding new mirror component on %s" % tgt_node)
3605
      if not rpc.call_blockdev_addchildren(tgt_node, dev, new_lvs):
3606
        logger.Error("Can't add local storage to drbd!")
3607
        for new_lv in new_lvs:
3608
          if not rpc.call_blockdev_remove(tgt_node, new_lv):
3609
            logger.Error("Can't rollback device %s")
3610
        return
3611

  
3612
      dev.children = new_lvs
3613
      cfg.Update(instance)
3614

  
3615

  
3616
    # this can fail as the old devices are degraded and _WaitForSync
3617
    # does a combined result over all disks, so we don't check its
3618
    # return value
3619
    logger.Info("Done changing drbd configs, waiting for sync")
3620
    _WaitForSync(cfg, instance, unlock=True)
3621

  
3622
    # so check manually all the devices
3623
    for name, (dev, old_lvs, new_lvs) in iv_names.iteritems():
3624
      cfg.SetDiskID(dev, instance.primary_node)
3625
      is_degr = rpc.call_blockdev_find(instance.primary_node, dev)[5]
3626
      if is_degr:
3627
        raise errors.OpExecError("DRBD device %s is degraded!" % name)
3628

  
3629
    for name, (dev, old_lvs, new_lvs) in iv_names.iteritems():
3630
      logger.Info("remove logical volumes for %s" % name)
3631
      for lv in old_lvs:
3632
        cfg.SetDiskID(lv, tgt_node)
3633
        if not rpc.call_blockdev_remove(tgt_node, lv):
3634
          logger.Error("Can't cleanup child device, skipping. You need to"
3635
                       " fix manually!")
3636
          continue
3637

  
3638
  def _ExecD8Secondary(self, feedback_fn):
3639
    """Replace the secondary node for drbd8.
3640

  
3641
    The algorithm for replace is quite complicated:
3642
      - for all disks of the instance:
3643
        - create new LVs on the new node with same names
3644
        - shutdown the drbd device on the old secondary
3645
        - disconnect the drbd network on the primary
3646
        - create the drbd device on the new secondary
3647
        - network attach the drbd on the primary, using an artifice:
3648
          the drbd code for Attach() will connect to the network if it
3649
          finds a device which is connected to the good local disks but
3650
          not network enabled
3651
      - wait for sync across all devices
3652
      - remove all disks from the old secondary
3653

  
3654
    Failures are not very well handled.
3655
    """
3656
    instance = self.instance
3657
    iv_names = {}
3658
    vgname = self.cfg.GetVGName()
3659
    # start of work
3660
    cfg = self.cfg
3661
    old_node = self.tgt_node
3662
    new_node = self.new_node
3663
    pri_node = instance.primary_node
3664
    for dev in instance.disks:
3665
      size = dev.size
3666
      logger.Info("adding new local storage on %s for %s" %
3667
                  (new_node, dev.iv_name))
3668
      # since we *always* want to create this LV, we use the
3669
      # _Create...OnPrimary (which forces the creation), even if we
3670
      # are talking about the secondary node
3671
      for new_lv in dev.children:
3672
        if not _CreateBlockDevOnPrimary(cfg, new_node, new_lv,
3673
                                        _GetInstanceInfoText(instance)):
3674
          raise errors.OpExecError("Failed to create new LV named '%s' on"
3675
                                   " node '%s'" %
3676
                                   (new_lv.logical_id[1], new_node))
3677

  
3678
      # create new devices on new_node
3679
      new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
3680
                              logical_id=(pri_node, new_node,
3681
                                          dev.logical_id[2]),
3682
                              children=dev.children)
3683
      if not _CreateBlockDevOnSecondary(cfg, new_node, new_drbd, False,
3684
                                      _GetInstanceInfoText(instance)):
3685
        raise errors.OpExecError("Failed to create new DRBD on"
3686
                                 " node '%s'" % new_node)
3687

  
3688
      # we have new devices, shutdown the drbd on the old secondary
3689
      cfg.SetDiskID(dev, old_node)
3690
      if not rpc.call_blockdev_shutdown(old_node, dev):
3691
        raise errors.OpExecError("Failed to shutdown DRBD on old node")
3692

  
3693
      # we have new storage, we 'rename' the network on the primary
3694
      cfg.SetDiskID(dev, pri_node)
3695
      # rename to the ip of the new node
3696
      new_uid = list(dev.physical_id)
3697
      new_uid[2] = self.remote_node_info.secondary_ip
3698
      rlist = [(dev, tuple(new_uid))]
3699
      if not rpc.call_blockdev_rename(pri_node, rlist):
3700
        raise errors.OpExecError("Can't detach re-attach drbd %s on node"
3701
                                 " %s from %s to %s" %
3702
                                 (dev.iv_name, pri_node, old_node, new_node))
3703
      dev.logical_id = (pri_node, new_node, dev.logical_id[2])
3704
      cfg.SetDiskID(dev, pri_node)
3705
      cfg.Update(instance)
3706

  
3707
      iv_names[dev.iv_name] = (dev, dev.children)
3708

  
3709
    # this can fail as the old devices are degraded and _WaitForSync
3710
    # does a combined result over all disks, so we don't check its
3711
    # return value
3712
    logger.Info("Done changing drbd configs, waiting for sync")
3713
    _WaitForSync(cfg, instance, unlock=True)
3714

  
3715
    # so check manually all the devices
3716
    for name, (dev, old_lvs) in iv_names.iteritems():
3717
      cfg.SetDiskID(dev, pri_node)
3718
      is_degr = rpc.call_blockdev_find(pri_node, dev)[5]
3719
      if is_degr:
3720
        raise errors.OpExecError("DRBD device %s is degraded!" % name)
3721

  
3722
    for name, (dev, old_lvs) in iv_names.iteritems():
3723
      logger.Info("remove logical volumes for %s" % name)
3724
      for lv in old_lvs:
3725
        cfg.SetDiskID(lv, old_node)
3726
        if not rpc.call_blockdev_remove(old_node, lv):
3727
          logger.Error("Can't cleanup child device, skipping. You need to"
3728
                       " fix manually!")
3729
          continue
3730

  
3731
  def Exec(self, feedback_fn):
3732
    """Execute disk replacement.
3733

  
3734
    This dispatches the disk replacement to the appropriate handler.
3735

  
3736
    """
3737
    instance = self.instance
3738
    if instance.disk_template == constants.DT_REMOTE_RAID1:
3739
      fn = self._ExecRR1
3740
    elif instance.disk_template == constants.DT_DRBD8:
3741
      if self.op.remote_node is None:
3742
        fn = self._ExecD8DiskOnly
3743
      else:
3744
        fn = self._ExecD8Secondary
3745
    else:
3746
      raise errors.ProgrammerError("Unhandled disk replacement case")
3747
    return fn(feedback_fn)
3748

  
3482 3749

  
3483 3750
class LUQueryInstanceData(NoHooksLU):
3484 3751
  """Query runtime instance data.

Also available in: Unified diff