Revision 735e1318
b/NEWS | ||
---|---|---|
9 | 9 |
|
10 | 10 |
- Deprecated ``admin_up`` field. Instead, ``admin_state`` is introduced, |
11 | 11 |
with 3 possible values -- ``up``, ``down`` and ``offline``. |
12 |
- Replaced ``--disks`` option of ``gnt-instance replace-disks`` with a |
|
13 |
more flexible ``--disk`` option. Now disk size and mode can be changed |
|
14 |
upon recreation. |
|
12 | 15 |
|
13 | 16 |
|
14 | 17 |
Version 2.5.0 rc5 |
b/lib/client/gnt_instance.py | ||
---|---|---|
39 | 39 |
from ganeti import netutils |
40 | 40 |
from ganeti import ssh |
41 | 41 |
from ganeti import objects |
42 |
from ganeti import ht |
|
42 | 43 |
|
43 | 44 |
|
44 | 45 |
_EXPAND_CLUSTER = "cluster" |
... | ... | |
601 | 602 |
|
602 | 603 |
""" |
603 | 604 |
instance_name = args[0] |
605 |
|
|
606 |
disks = [] |
|
607 |
|
|
604 | 608 |
if opts.disks: |
605 |
try: |
|
606 |
opts.disks = [int(v) for v in opts.disks.split(",")] |
|
607 |
except (ValueError, TypeError), err: |
|
608 |
ToStderr("Invalid disks value: %s" % str(err)) |
|
609 |
return 1 |
|
610 |
else: |
|
611 |
opts.disks = [] |
|
609 |
for didx, ddict in opts.disks: |
|
610 |
didx = int(didx) |
|
611 |
|
|
612 |
if not ht.TDict(ddict): |
|
613 |
msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict) |
|
614 |
raise errors.OpPrereqError(msg) |
|
615 |
|
|
616 |
if constants.IDISK_SIZE in ddict: |
|
617 |
try: |
|
618 |
ddict[constants.IDISK_SIZE] = \ |
|
619 |
utils.ParseUnit(ddict[constants.IDISK_SIZE]) |
|
620 |
except ValueError, err: |
|
621 |
raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % |
|
622 |
(didx, err)) |
|
623 |
|
|
624 |
disks.append((didx, ddict)) |
|
625 |
|
|
626 |
# TODO: Verify modifyable parameters (already done in |
|
627 |
# LUInstanceRecreateDisks, but it'd be nice to have in the client) |
|
612 | 628 |
|
613 | 629 |
if opts.node: |
614 | 630 |
pnode, snode = SplitNodeOption(opts.node) |
... | ... | |
619 | 635 |
nodes = [] |
620 | 636 |
|
621 | 637 |
op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name, |
622 |
disks=opts.disks, |
|
623 |
nodes=nodes) |
|
638 |
disks=disks, nodes=nodes) |
|
624 | 639 |
SubmitOrSend(op, opts) |
640 |
|
|
625 | 641 |
return 0 |
626 | 642 |
|
627 | 643 |
|
... | ... | |
1545 | 1561 |
"[-f] <instance>", "Deactivate an instance's disks"), |
1546 | 1562 |
"recreate-disks": ( |
1547 | 1563 |
RecreateDisks, ARGS_ONE_INSTANCE, |
1548 |
[SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
|
|
1564 |
[SUBMIT_OPT, DISK_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT], |
|
1549 | 1565 |
"<instance>", "Recreate an instance's disks"), |
1550 | 1566 |
"grow-disk": ( |
1551 | 1567 |
GrowDisk, |
b/lib/cmdlib.py | ||
---|---|---|
6901 | 6901 |
HTYPE = constants.HTYPE_INSTANCE |
6902 | 6902 |
REQ_BGL = False |
6903 | 6903 |
|
6904 |
_MODIFYABLE = frozenset([ |
|
6905 |
constants.IDISK_SIZE, |
|
6906 |
constants.IDISK_MODE, |
|
6907 |
]) |
|
6908 |
|
|
6909 |
# New or changed disk parameters may have different semantics |
|
6910 |
assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([ |
|
6911 |
constants.IDISK_ADOPT, |
|
6912 |
|
|
6913 |
# TODO: Implement support changing VG while recreating |
|
6914 |
constants.IDISK_VG, |
|
6915 |
constants.IDISK_METAVG, |
|
6916 |
])) |
|
6917 |
|
|
6904 | 6918 |
def CheckArguments(self): |
6905 |
# normalise the disk list |
|
6906 |
self.op.disks = sorted(frozenset(self.op.disks)) |
|
6919 |
if self.op.disks and ht.TPositiveInt(self.op.disks[0]): |
|
6920 |
# Normalize and convert deprecated list of disk indices |
|
6921 |
self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))] |
|
6922 |
|
|
6923 |
duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks)) |
|
6924 |
if duplicates: |
|
6925 |
raise errors.OpPrereqError("Some disks have been specified more than" |
|
6926 |
" once: %s" % utils.CommaJoin(duplicates), |
|
6927 |
errors.ECODE_INVAL) |
|
6928 |
|
|
6929 |
for (idx, params) in self.op.disks: |
|
6930 |
utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES) |
|
6931 |
unsupported = frozenset(params.keys()) - self._MODIFYABLE |
|
6932 |
if unsupported: |
|
6933 |
raise errors.OpPrereqError("Parameters for disk %s try to change" |
|
6934 |
" unmodifyable parameter(s): %s" % |
|
6935 |
(idx, utils.CommaJoin(unsupported)), |
|
6936 |
errors.ECODE_INVAL) |
|
6907 | 6937 |
|
6908 | 6938 |
def ExpandNames(self): |
6909 | 6939 |
self._ExpandAndLockInstance() |
... | ... | |
6969 | 6999 |
if instance.disk_template == constants.DT_DISKLESS: |
6970 | 7000 |
raise errors.OpPrereqError("Instance '%s' has no disks" % |
6971 | 7001 |
self.op.instance_name, errors.ECODE_INVAL) |
7002 |
|
|
6972 | 7003 |
# if we replace nodes *and* the old primary is offline, we don't |
6973 | 7004 |
# check |
6974 | 7005 |
assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE) |
... | ... | |
6978 | 7009 |
_CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING, |
6979 | 7010 |
msg="cannot recreate disks") |
6980 | 7011 |
|
6981 |
if not self.op.disks:
|
|
6982 |
self.op.disks = range(len(instance.disks))
|
|
7012 |
if self.op.disks: |
|
7013 |
self.disks = dict(self.op.disks)
|
|
6983 | 7014 |
else: |
6984 |
for idx in self.op.disks: |
|
6985 |
if idx >= len(instance.disks): |
|
6986 |
raise errors.OpPrereqError("Invalid disk index '%s'" % idx, |
|
6987 |
errors.ECODE_INVAL) |
|
6988 |
if self.op.disks != range(len(instance.disks)) and self.op.nodes: |
|
7015 |
self.disks = dict((idx, {}) for idx in range(len(instance.disks))) |
|
7016 |
|
|
7017 |
maxidx = max(self.disks.keys()) |
|
7018 |
if maxidx >= len(instance.disks): |
|
7019 |
raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx, |
|
7020 |
errors.ECODE_INVAL) |
|
7021 |
|
|
7022 |
if (self.op.nodes and |
|
7023 |
sorted(self.disks.keys()) != range(len(instance.disks))): |
|
6989 | 7024 |
raise errors.OpPrereqError("Can't recreate disks partially and" |
6990 | 7025 |
" change the nodes at the same time", |
6991 | 7026 |
errors.ECODE_INVAL) |
7027 |
|
|
6992 | 7028 |
self.instance = instance |
6993 | 7029 |
|
6994 | 7030 |
def Exec(self, feedback_fn): |
... | ... | |
7001 | 7037 |
self.owned_locks(locking.LEVEL_NODE_RES)) |
7002 | 7038 |
|
7003 | 7039 |
to_skip = [] |
7004 |
mods = [] # keeps track of needed logical_id changes
|
|
7040 |
mods = [] # keeps track of needed changes |
|
7005 | 7041 |
|
7006 | 7042 |
for idx, disk in enumerate(instance.disks): |
7007 |
if idx not in self.op.disks: # disk idx has not been passed in |
|
7043 |
try: |
|
7044 |
changes = self.disks[idx] |
|
7045 |
except KeyError: |
|
7046 |
# Disk should not be recreated |
|
7008 | 7047 |
to_skip.append(idx) |
7009 | 7048 |
continue |
7049 |
|
|
7010 | 7050 |
# update secondaries for disks, if needed |
7011 |
if self.op.nodes: |
|
7012 |
if disk.dev_type == constants.LD_DRBD8: |
|
7013 |
# need to update the nodes and minors |
|
7014 |
assert len(self.op.nodes) == 2 |
|
7015 |
assert len(disk.logical_id) == 6 # otherwise disk internals |
|
7016 |
# have changed |
|
7017 |
(_, _, old_port, _, _, old_secret) = disk.logical_id |
|
7018 |
new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name) |
|
7019 |
new_id = (self.op.nodes[0], self.op.nodes[1], old_port, |
|
7020 |
new_minors[0], new_minors[1], old_secret) |
|
7021 |
assert len(disk.logical_id) == len(new_id) |
|
7022 |
mods.append((idx, new_id)) |
|
7051 |
if self.op.nodes and disk.dev_type == constants.LD_DRBD8: |
|
7052 |
# need to update the nodes and minors |
|
7053 |
assert len(self.op.nodes) == 2 |
|
7054 |
assert len(disk.logical_id) == 6 # otherwise disk internals |
|
7055 |
# have changed |
|
7056 |
(_, _, old_port, _, _, old_secret) = disk.logical_id |
|
7057 |
new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name) |
|
7058 |
new_id = (self.op.nodes[0], self.op.nodes[1], old_port, |
|
7059 |
new_minors[0], new_minors[1], old_secret) |
|
7060 |
assert len(disk.logical_id) == len(new_id) |
|
7061 |
else: |
|
7062 |
new_id = None |
|
7063 |
|
|
7064 |
mods.append((idx, new_id, changes)) |
|
7023 | 7065 |
|
7024 | 7066 |
# now that we have passed all asserts above, we can apply the mods |
7025 | 7067 |
# in a single run (to avoid partial changes) |
7026 |
for idx, new_id in mods: |
|
7027 |
instance.disks[idx].logical_id = new_id |
|
7068 |
for idx, new_id, changes in mods: |
|
7069 |
disk = instance.disks[idx] |
|
7070 |
if new_id is not None: |
|
7071 |
assert disk.dev_type == constants.LD_DRBD8 |
|
7072 |
disk.logical_id = new_id |
|
7073 |
if changes: |
|
7074 |
disk.Update(size=changes.get(constants.IDISK_SIZE, None), |
|
7075 |
mode=changes.get(constants.IDISK_MODE, None)) |
|
7028 | 7076 |
|
7029 | 7077 |
# change primary node, if needed |
7030 | 7078 |
if self.op.nodes: |
b/lib/objects.py | ||
---|---|---|
730 | 730 |
raise errors.ProgrammerError("Disk.RecordGrow called for unsupported" |
731 | 731 |
" disk type %s" % self.dev_type) |
732 | 732 |
|
733 |
def Update(self, size=None, mode=None): |
|
734 |
"""Apply changes to size and mode. |
|
735 |
|
|
736 |
""" |
|
737 |
if self.dev_type == constants.LD_DRBD8: |
|
738 |
if self.children: |
|
739 |
self.children[0].Update(size=size, mode=mode) |
|
740 |
else: |
|
741 |
assert not self.children |
|
742 |
|
|
743 |
if size is not None: |
|
744 |
self.size = size |
|
745 |
if mode is not None: |
|
746 |
self.mode = mode |
|
747 |
|
|
733 | 748 |
def UnsetSize(self): |
734 | 749 |
"""Sets recursively the size to zero for the disk and its children. |
735 | 750 |
|
b/lib/opcodes.py | ||
---|---|---|
180 | 180 |
ht.TListOf(ht.TAnd(ht.TIsLength(len(_TSetParamsResultItemItems)), |
181 | 181 |
ht.TItems(_TSetParamsResultItemItems))) |
182 | 182 |
|
183 |
# TODO: Generate check from constants.IDISK_PARAMS_TYPES (however, not all users |
|
184 |
# of this check support all parameters) |
|
185 |
_TDiskParams = ht.TDictOf(ht.TElemOf(constants.IDISK_PARAMS), |
|
186 |
ht.TOr(ht.TNonEmptyString, ht.TInt)) |
|
187 |
|
|
183 | 188 |
_SUMMARY_PREFIX = { |
184 | 189 |
"CLUSTER_": "C_", |
185 | 190 |
"GROUP_": "G_", |
... | ... | |
1089 | 1094 |
_PNameCheck, |
1090 | 1095 |
_PIgnoreIpolicy, |
1091 | 1096 |
("beparams", ht.EmptyDict, ht.TDict, "Backend parameters for instance"), |
1092 |
("disks", ht.NoDefault, |
|
1093 |
# TODO: Generate check from constants.IDISK_PARAMS_TYPES |
|
1094 |
ht.TListOf(ht.TDictOf(ht.TElemOf(constants.IDISK_PARAMS), |
|
1095 |
ht.TOr(ht.TNonEmptyString, ht.TInt))), |
|
1097 |
("disks", ht.NoDefault, ht.TListOf(_TDiskParams), |
|
1096 | 1098 |
"Disk descriptions, for example ``[{\"%s\": 100}, {\"%s\": 5}]``;" |
1097 | 1099 |
" each disk definition must contain a ``%s`` value and" |
1098 | 1100 |
" can contain an optional ``%s`` value denoting the disk access mode" |
... | ... | |
1323 | 1325 |
|
1324 | 1326 |
class OpInstanceRecreateDisks(OpCode): |
1325 | 1327 |
"""Recreate an instance's disks.""" |
1328 |
_TDiskChanges = \ |
|
1329 |
ht.TAnd(ht.TIsLength(2), |
|
1330 |
ht.TItems([ht.Comment("Disk index")(ht.TPositiveInt), |
|
1331 |
ht.Comment("Parameters")(_TDiskParams)])) |
|
1332 |
|
|
1326 | 1333 |
OP_DSC_FIELD = "instance_name" |
1327 | 1334 |
OP_PARAMS = [ |
1328 | 1335 |
_PInstanceName, |
1329 |
("disks", ht.EmptyList, ht.TListOf(ht.TPositiveInt), |
|
1330 |
"List of disk indexes"), |
|
1336 |
("disks", ht.EmptyList, |
|
1337 |
ht.TOr(ht.TListOf(ht.TPositiveInt), ht.TListOf(_TDiskChanges)), |
|
1338 |
"List of disk indexes (deprecated) or a list of tuples containing a disk" |
|
1339 |
" index and a possibly empty dictionary with disk parameter changes"), |
|
1331 | 1340 |
("nodes", ht.EmptyList, ht.TListOf(ht.TNonEmptyString), |
1332 | 1341 |
"New instance nodes, if relocation is desired"), |
1333 | 1342 |
] |
b/man/gnt-instance.rst | ||
---|---|---|
1365 | 1365 |
RECREATE-DISKS |
1366 | 1366 |
^^^^^^^^^^^^^^ |
1367 | 1367 |
|
1368 |
**recreate-disks** [--submit] [--disks=``indices``] [-n node1:[node2]]
|
|
1369 |
{*instance*}
|
|
1368 |
| **recreate-disks** [--submit] [-n node1:[node2]]
|
|
1369 |
| [--disk=*N*[:[size=*VAL*][,mode=*ro\|rw*]]] {*instance*}
|
|
1370 | 1370 |
|
1371 |
Recreates the disks of the given instance, or only a subset of the |
|
1372 |
disks (if the option ``disks`` is passed, which must be a |
|
1373 |
comma-separated list of disk indices, starting from zero). |
|
1371 |
Recreates all or a subset of disks of the given instance. |
|
1374 | 1372 |
|
1375 | 1373 |
Note that this functionality should only be used for missing disks; if |
1376 | 1374 |
any of the given disks already exists, the operation will fail. While |
1377 | 1375 |
this is suboptimal, recreate-disks should hopefully not be needed in |
1378 | 1376 |
normal operation and as such the impact of this is low. |
1379 | 1377 |
|
1378 |
If only a subset should be recreated, any number of ``disk`` options can |
|
1379 |
be specified. It expects a disk index and an optional list of disk |
|
1380 |
parameters to change. Only ``size`` and ``mode`` can be changed while |
|
1381 |
recreating disks. To recreate all disks while changing parameters on |
|
1382 |
a subset only, a ``--disk`` option must be given for every disk of the |
|
1383 |
instance. |
|
1384 |
|
|
1380 | 1385 |
Optionally the instance's disks can be recreated on different |
1381 | 1386 |
nodes. This can be useful if, for example, the original nodes of the |
1382 | 1387 |
instance have gone down (and are marked offline), so we can't recreate |
1383 | 1388 |
on the same nodes. To do this, pass the new node(s) via ``-n`` option, |
1384 | 1389 |
with a syntax similar to the **add** command. The number of nodes |
1385 | 1390 |
passed must equal the number of nodes that the instance currently |
1386 |
has. Note that changing nodes is only allowed for 'all disk'
|
|
1387 |
replacement (when ``--disks`` is not passed).
|
|
1391 |
has. Note that changing nodes is only allowed when all disks are
|
|
1392 |
replaced, e.g. when no ``--disk`` option is passed.
|
|
1388 | 1393 |
|
1389 | 1394 |
The ``--submit`` option is used to send the job to the master daemon |
1390 | 1395 |
but not wait for its completion. The job ID will be shown so that it |
Also available in: Unified diff