Revision a71f835e

b/lib/client/gnt_instance.py
65 65
  ]
66 66

  
67 67

  
68
_MISSING = object()
68 69
_ENV_OVERRIDE = frozenset(["list"])
69 70

  
70 71

  
......
1267 1268
  return retcode
1268 1269

  
1269 1270

  
1271
def _ConvertNicDiskModifications(mods):
1272
  """Converts NIC/disk modifications from CLI to opcode.
1273

  
1274
  When L{opcodes.OpInstanceSetParams} was changed to support adding/removing
1275
  disks at arbitrary indices, its parameter format changed. This function
1276
  converts legacy requests (e.g. "--net add" or "--disk add:size=4G") to the
1277
  newer format and adds support for new-style requests (e.g. "--new 4:add").
1278

  
1279
  @type mods: list of tuples
1280
  @param mods: Modifications as given by command line parser
1281
  @rtype: list of tuples
1282
  @return: Modifications as understood by L{opcodes.OpInstanceSetParams}
1283

  
1284
  """
1285
  result = []
1286

  
1287
  for (idx, params) in mods:
1288
    if idx == constants.DDM_ADD:
1289
      # Add item as last item (legacy interface)
1290
      action = constants.DDM_ADD
1291
      idxno = -1
1292
    elif idx == constants.DDM_REMOVE:
1293
      # Remove last item (legacy interface)
1294
      action = constants.DDM_REMOVE
1295
      idxno = -1
1296
    else:
1297
      # Modifications and adding/removing at arbitrary indices
1298
      try:
1299
        idxno = int(idx)
1300
      except (TypeError, ValueError):
1301
        raise errors.OpPrereqError("Non-numeric index '%s'" % idx,
1302
                                   errors.ECODE_INVAL)
1303

  
1304
      add = params.pop(constants.DDM_ADD, _MISSING)
1305
      remove = params.pop(constants.DDM_REMOVE, _MISSING)
1306

  
1307
      if not (add is _MISSING or remove is _MISSING):
1308
        raise errors.OpPrereqError("Cannot add and remove at the same time",
1309
                                   errors.ECODE_INVAL)
1310
      elif add is not _MISSING:
1311
        action = constants.DDM_ADD
1312
      elif remove is not _MISSING:
1313
        action = constants.DDM_REMOVE
1314
      else:
1315
        action = constants.DDM_MODIFY
1316

  
1317
      assert not (constants.DDMS_VALUES_WITH_MODIFY & set(params.keys()))
1318

  
1319
    if action == constants.DDM_REMOVE and params:
1320
      raise errors.OpPrereqError("Not accepting parameters on removal",
1321
                                 errors.ECODE_INVAL)
1322

  
1323
    result.append((action, idxno, params))
1324

  
1325
  return result
1326

  
1327

  
1328
def _ParseDiskSizes(mods):
1329
  """Parses disk sizes in parameters.
1330

  
1331
  """
1332
  for (action, _, params) in mods:
1333
    if params and constants.IDISK_SIZE in params:
1334
      params[constants.IDISK_SIZE] = \
1335
        utils.ParseUnit(params[constants.IDISK_SIZE])
1336
    elif action == constants.DDM_ADD:
1337
      raise errors.OpPrereqError("Missing required parameter 'size'",
1338
                                 errors.ECODE_INVAL)
1339

  
1340
  return mods
1341

  
1342

  
1270 1343
def SetInstanceParams(opts, args):
1271 1344
  """Modifies an instance.
1272 1345

  
......
1301 1374
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1302 1375
                      allowed_values=[constants.VALUE_DEFAULT])
1303 1376

  
1304
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1305
    try:
1306
      nic_op = int(nic_op)
1307
      opts.nics[idx] = (nic_op, nic_dict)
1308
    except (TypeError, ValueError):
1309
      pass
1310

  
1311
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1312
    try:
1313
      disk_op = int(disk_op)
1314
      opts.disks[idx] = (disk_op, disk_dict)
1315
    except (TypeError, ValueError):
1316
      pass
1317
    if disk_op == constants.DDM_ADD:
1318
      if "size" not in disk_dict:
1319
        raise errors.OpPrereqError("Missing required parameter 'size'",
1320
                                   errors.ECODE_INVAL)
1321
      disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1377
  nics = _ConvertNicDiskModifications(opts.nics)
1378
  disks = _ParseDiskSizes(_ConvertNicDiskModifications(opts.disks))
1322 1379

  
1323 1380
  if (opts.disk_template and
1324 1381
      opts.disk_template in constants.DTS_INT_MIRROR and
......
1335 1392
    offline = None
1336 1393

  
1337 1394
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1338
                                   nics=opts.nics,
1339
                                   disks=opts.disks,
1395
                                   nics=nics,
1396
                                   disks=disks,
1340 1397
                                   disk_template=opts.disk_template,
1341 1398
                                   remote_node=opts.node,
1342 1399
                                   hvparams=opts.hvparams,
b/man/gnt-instance.rst
896 896
by ballooning it up or down to the new value.
897 897

  
898 898
The ``--disk add:size=``*SIZE* option adds a disk to the instance. The
899
optional ``vg=``*VG* option specifies LVM volume group other than
900
default vg to create the disk on. For DRBD disks, the ``metavg=``*VG*
901
option specifies the volume group for the metadata device. The
902
``--disk remove`` option will remove the last disk of the
903
instance. The ``--disk`` *N*``:mode=``*MODE* option will change the
904
mode of the Nth disk of the instance between read-only (``ro``) and
899
optional ``vg=``*VG* option specifies an LVM volume group other than
900
the default volume group to create the disk on. For DRBD disks, the
901
``metavg=``*VG* option specifies the volume group for the metadata
902
device. ``--disk`` *N*``:add,size=``**SIZE** can be used to add a
903
disk at a specific index. The ``--disk remove`` option will remove the
904
last disk of the instance. Use ``--disk ``*N*``:remove`` to remove a
905
disk by its index. The ``--disk`` *N*``:mode=``*MODE* option will change
906
the mode of the Nth disk of the instance between read-only (``ro``) and
905 907
read-write (``rw``).
906 908

  
907
The ``--net add:``*options* option will add a new NIC to the
908
instance. The available options are the same as in the **add** command
909
(mac, ip, link, mode). The ``--net remove`` will remove the last NIC
910
of the instance, while the ``--net`` *N*:*options* option will change
911
the parameters of the Nth instance NIC.
909
The ``--net add:``*options* and ``--net`` *N*``:add,``*options* option
910
will add a new network interface to the instance. The available options
911
are the same as in the **add** command (``mac``, ``ip``, ``link``,
912
``mode``). The ``--net remove`` will remove the last network interface
913
of the instance (``--net`` *N*``:remove`` for a specific index), while
914
the ``--net`` *N*``:``*options* option will change the parameters of the Nth
915
instance network interface.
912 916

  
913 917
The option ``-o (--os-type)`` will change the OS name for the instance
914 918
(without reinstallation). In case an OS variant is specified that is
b/test/ganeti.client.gnt_instance_unittest.py
119 119
    self.assertEqual(len(self._output), 0)
120 120

  
121 121

  
122
class TestConvertNicDiskModifications(unittest.TestCase):
123
  def test(self):
124
    fn = gnt_instance._ConvertNicDiskModifications
125

  
126
    self.assertEqual(fn([]), [])
127

  
128
    # Error cases
129
    self.assertRaises(errors.OpPrereqError, fn, [
130
      (constants.DDM_REMOVE, { "param": "value", }),
131
      ])
132
    self.assertRaises(errors.OpPrereqError, fn, [
133
      (0, { constants.DDM_REMOVE: True, "param": "value", }),
134
      ])
135
    self.assertRaises(errors.OpPrereqError, fn, [
136
      ("Hello", {}),
137
      ])
138
    self.assertRaises(errors.OpPrereqError, fn, [
139
      (0, {
140
        constants.DDM_REMOVE: True,
141
        constants.DDM_ADD: True,
142
        }),
143
      ])
144

  
145
    # Legacy calls
146
    for action in constants.DDMS_VALUES:
147
      self.assertEqual(fn([
148
        (action, {}),
149
        ]), [
150
        (action, -1, {}),
151
        ])
152

  
153
    self.assertEqual(fn([
154
      (constants.DDM_ADD, {
155
        constants.IDISK_SIZE: 1024,
156
        }),
157
      ]), [
158
      (constants.DDM_ADD, -1, {
159
        constants.IDISK_SIZE: 1024,
160
        }),
161
      ])
162

  
163
    # New-style calls
164
    self.assertEqual(fn([
165
      (2, {
166
        constants.IDISK_MODE: constants.DISK_RDWR,
167
        }),
168
      ]), [
169
      (constants.DDM_MODIFY, 2, {
170
        constants.IDISK_MODE: constants.DISK_RDWR,
171
        }),
172
      ])
173

  
174
    self.assertEqual(fn([
175
      (0, {
176
        constants.DDM_ADD: True,
177
        constants.IDISK_SIZE: 4096,
178
        }),
179
      ]), [
180
      (constants.DDM_ADD, 0, {
181
        constants.IDISK_SIZE: 4096,
182
        }),
183
      ])
184

  
185
    self.assertEqual(fn([
186
      (-1, {
187
        constants.DDM_REMOVE: True,
188
        }),
189
      ]), [
190
      (constants.DDM_REMOVE, -1, {}),
191
      ])
192

  
193

  
194
class TestParseDiskSizes(unittest.TestCase):
195
  def test(self):
196
    fn = gnt_instance._ParseDiskSizes
197

  
198
    self.assertEqual(fn([]), [])
199

  
200
    # Missing size parameter
201
    self.assertRaises(errors.OpPrereqError, fn, [
202
      (constants.DDM_ADD, 0, {}),
203
      ])
204

  
205
    # Converting disk size
206
    self.assertEqual(fn([
207
      (constants.DDM_ADD, 11, {
208
        constants.IDISK_SIZE: "9G",
209
        }),
210
      ]), [
211
        (constants.DDM_ADD, 11, {
212
          constants.IDISK_SIZE: 9216,
213
          }),
214
        ])
215

  
216
    # No size parameter
217
    self.assertEqual(fn([
218
      (constants.DDM_REMOVE, 11, {
219
        "other": "24M",
220
        }),
221
      ]), [
222
        (constants.DDM_REMOVE, 11, {
223
          "other": "24M",
224
          }),
225
        ])
226

  
227

  
122 228
if __name__ == "__main__":
123 229
  testutils.GanetiTestProgram()

Also available in: Unified diff