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