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