Revision ef99e3e8

b/lib/cli.py
680 680
  return retval
681 681

  
682 682

  
683
def check_list_ident_key_val(_, opt, value):
684
  """Custom parser for "ident:key=val,key=val/ident:key=val" options.
683
def check_multilist_ident_key_val(_, opt, value):
684
  """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options.
685 685

  
686 686
  @rtype: list of dictionary
687
  @return: {ident: {key: val, key: val}, ident: {key: val}}
687
  @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}]
688 688

  
689 689
  """
690
  return _SplitListKeyVal(opt, value)
690
  retval = []
691
  for line in value.split("//"):
692
    retval.append(_SplitListKeyVal(opt, line))
693
  return retval
691 694

  
692 695

  
693 696
def check_bool(option, opt, value): # pylint: disable=W0613
......
762 765
    "completion_suggest",
763 766
    ]
764 767
  TYPES = Option.TYPES + (
765
    "listidentkeyval",
768
    "multilistidentkeyval",
766 769
    "identkeyval",
767 770
    "keyval",
768 771
    "unit",
......
771 774
    "maybefloat",
772 775
    )
773 776
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
774
  TYPE_CHECKER["listidentkeyval"] = check_list_ident_key_val
777
  TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
775 778
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
776 779
  TYPE_CHECKER["keyval"] = check_key_val
777 780
  TYPE_CHECKER["unit"] = check_unit
......
964 967
IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs"
965 968
IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR,
966 969
                                      dest="ipolicy_bounds_specs",
967
                                      type="listidentkeyval", default=None,
970
                                      type="multilistidentkeyval", default=None,
968 971
                                      help="Complete instance specs limits")
969 972

  
970 973
IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
......
3796 3799
    if stdspecs:
3797 3800
      buf.write(" %s " % IPOLICY_STD_SPECS_STR)
3798 3801
      _PrintSpecsParameters(buf, stdspecs)
3799
  minmax = ipolicy.get("minmax")
3800
  if minmax:
3801
    minspecs = minmax[0].get("min")
3802
    maxspecs = minmax[0].get("max")
3802
  minmaxes = ipolicy.get("minmax", [])
3803
  first = True
3804
  for minmax in minmaxes:
3805
    minspecs = minmax.get("min")
3806
    maxspecs = minmax.get("max")
3803 3807
    if minspecs and maxspecs:
3804
      buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
3808
      if first:
3809
        buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
3810
        first = False
3811
      else:
3812
        buf.write("//")
3805 3813
      buf.write("min:")
3806 3814
      _PrintSpecsParameters(buf, minspecs)
3807 3815
      buf.write("/max:")
......
3945 3953

  
3946 3954
def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values):
3947 3955
  ret = None
3948
  if minmax_ispecs and allowed_values and len(minmax_ispecs) == 1:
3949
    for (key, spec) in minmax_ispecs.items():
3956
  if (minmax_ispecs and allowed_values and len(minmax_ispecs) == 1 and
3957
      len(minmax_ispecs[0]) == 1):
3958
    for (key, spec) in minmax_ispecs[0].items():
3950 3959
      # This loop is executed exactly once
3951 3960
      if key in allowed_values and not spec:
3952 3961
        ret = key
......
3959 3968
  if found_allowed is not None:
3960 3969
    ipolicy_out[constants.ISPECS_MINMAX] = found_allowed
3961 3970
  elif minmax_ispecs is not None:
3962
    minmax_out = {}
3963
    for (key, spec) in minmax_ispecs.items():
3964
      if key not in constants.ISPECS_MINMAX_KEYS:
3965
        msg = "Invalid key in bounds instance specifications: %s" % key
3966
        raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
3967
      minmax_out[key] = _ParseISpec(spec, key, True)
3968
    ipolicy_out[constants.ISPECS_MINMAX] = [minmax_out]
3971
    minmax_out = []
3972
    for mmpair in minmax_ispecs:
3973
      mmpair_out = {}
3974
      for (key, spec) in mmpair.items():
3975
        if key not in constants.ISPECS_MINMAX_KEYS:
3976
          msg = "Invalid key in bounds instance specifications: %s" % key
3977
          raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
3978
        mmpair_out[key] = _ParseISpec(spec, key, True)
3979
      minmax_out.append(mmpair_out)
3980
    ipolicy_out[constants.ISPECS_MINMAX] = minmax_out
3969 3981
  if std_ispecs is not None:
3970 3982
    assert not group_ipolicy # This is not an option for gnt-group
3971 3983
    ipolicy_out[constants.ISPECS_STD] = _ParseISpec(std_ispecs, "std", False)
b/man/gnt-cluster.rst
508 508
The ``--specs-...`` and ``--ipolicy-...`` options specify the instance
509 509
policy on the cluster. The ``--ipolicy-bounds-specs`` option sets the
510 510
minimum and maximum specifications for instances. The format is:
511
min:*param*=*value*,.../max:*param*=*value*,... The
511
min:*param*=*value*,.../max:*param*=*value*,... and further
512
specifications pairs can be added by using ``//`` as a separator. The
512 513
``--ipolicy-std-specs`` option takes a list of parameter/value pairs.
513 514
For both options, *param* can be:
514 515

  
b/test/py/ganeti.cli_unittest.py
141 141
        self.assertEqual(self._csikv(arg), res)
142 142

  
143 143

  
144
class TestListIdentKeyVal(unittest.TestCase):
145
  """Test for cli.check_list_ident_key_val()"""
144
class TestMultilistIdentKeyVal(unittest.TestCase):
145
  """Test for cli.check_multilist_ident_key_val()"""
146 146

  
147 147
  @staticmethod
148
  def _clikv(value):
149
    return cli.check_list_ident_key_val("option", "opt", value)
148
  def _cmikv(value):
149
    return cli.check_multilist_ident_key_val("option", "opt", value)
150 150

  
151 151
  def testListIdentKeyVal(self):
152 152
    test_cases = [
153 153
      ("",
154 154
       None),
155
      ("foo",
156
       {"foo": {}}),
157
      ("foo:bar=baz",
158
       {"foo": {"bar": "baz"}}),
155
      ("foo", [
156
        {"foo": {}}
157
        ]),
158
      ("foo:bar=baz", [
159
        {"foo": {"bar": "baz"}}
160
        ]),
159 161
      ("foo:bar=baz/foo:bat=bad",
160 162
       None),
161
      ("foo:abc=42/bar:def=11",
162
       {"foo": {"abc": "42"},
163
        "bar": {"def": "11"}}),
164
      ("foo:abc=42/bar:def=11,ghi=07",
165
       {"foo": {"abc": "42"},
166
        "bar": {"def": "11", "ghi": "07"}}),
163
      ("foo:abc=42/bar:def=11", [
164
        {"foo": {"abc": "42"},
165
         "bar": {"def": "11"}}
166
        ]),
167
      ("foo:abc=42/bar:def=11,ghi=07", [
168
        {"foo": {"abc": "42"},
169
         "bar": {"def": "11", "ghi": "07"}}
170
        ]),
171
      ("foo:abc=42/bar:def=11//",
172
       None),
173
      ("foo:abc=42/bar:def=11,ghi=07//foobar", [
174
        {"foo": {"abc": "42"},
175
         "bar": {"def": "11", "ghi": "07"}},
176
        {"foobar": {}}
177
        ]),
178
      ("foo:abc=42/bar:def=11,ghi=07//foobar:xyz=88", [
179
        {"foo": {"abc": "42"},
180
         "bar": {"def": "11", "ghi": "07"}},
181
        {"foobar": {"xyz": "88"}}
182
        ]),
183
      ("foo:abc=42/bar:def=11,ghi=07//foobar:xyz=88/foo:uvw=314", [
184
        {"foo": {"abc": "42"},
185
         "bar": {"def": "11", "ghi": "07"}},
186
        {"foobar": {"xyz": "88"},
187
         "foo": {"uvw": "314"}}
188
        ]),
167 189
      ]
168 190
    for (arg, res) in test_cases:
169 191
      if res is None:
170
        self.assertRaises(ParameterError, self._clikv, arg)
192
        self.assertRaises(ParameterError, self._cmikv, arg)
171 193
      else:
172
        self.assertEqual(res, self._clikv(arg))
194
        self.assertEqual(res, self._cmikv(arg))
173 195

  
174 196

  
175 197
class TestToStream(unittest.TestCase):
......
1381 1403
                      ipolicy_vcpu_ratio=None, ipolicy_spindle_ratio=None,
1382 1404
                      fill_all=True)
1383 1405

  
1384
    good_mmspecs = constants.ISPECS_MINMAX_DEFAULTS
1406
    good_mmspecs = [
1407
      constants.ISPECS_MINMAX_DEFAULTS,
1408
      constants.ISPECS_MINMAX_DEFAULTS,
1409
      ]
1385 1410
    self._TestInvalidISpecs(good_mmspecs, None, fail=False)
1386 1411
    broken_mmspecs = copy.deepcopy(good_mmspecs)
1387
    for key in constants.ISPECS_MINMAX_KEYS:
1388
      for par in constants.ISPECS_PARAMETERS:
1389
        old = broken_mmspecs[key][par]
1390
        del broken_mmspecs[key][par]
1412
    for minmaxpair in broken_mmspecs:
1413
      for key in constants.ISPECS_MINMAX_KEYS:
1414
        for par in constants.ISPECS_PARAMETERS:
1415
          old = minmaxpair[key][par]
1416
          del minmaxpair[key][par]
1417
          self._TestInvalidISpecs(broken_mmspecs, None)
1418
          minmaxpair[key][par] = "invalid"
1419
          self._TestInvalidISpecs(broken_mmspecs, None)
1420
          minmaxpair[key][par] = old
1421
        minmaxpair[key]["invalid_key"] = None
1391 1422
        self._TestInvalidISpecs(broken_mmspecs, None)
1392
        broken_mmspecs[key][par] = "invalid"
1393
        self._TestInvalidISpecs(broken_mmspecs, None)
1394
        broken_mmspecs[key][par] = old
1395
      broken_mmspecs[key]["invalid_key"] = None
1423
        del minmaxpair[key]["invalid_key"]
1424
      minmaxpair["invalid_key"] = None
1396 1425
      self._TestInvalidISpecs(broken_mmspecs, None)
1397
      del broken_mmspecs[key]["invalid_key"]
1398
    broken_mmspecs["invalid_key"] = None
1399
    self._TestInvalidISpecs(broken_mmspecs, None)
1400
    del broken_mmspecs["invalid_key"]
1401
    assert broken_mmspecs == good_mmspecs
1426
      del minmaxpair["invalid_key"]
1427
      assert broken_mmspecs == good_mmspecs
1402 1428

  
1403 1429
    good_stdspecs = constants.IPOLICY_DEFAULTS[constants.ISPECS_STD]
1404 1430
    self._TestInvalidISpecs(None, good_stdspecs, fail=False)
......
1421 1447
      constants.IPOLICY_VCPU_RATIO: allowedv,
1422 1448
      constants.IPOLICY_SPINDLE_RATIO: allowedv,
1423 1449
      }
1424
    pol1 = cli.CreateIPolicyFromOpts(minmax_ispecs={allowedv: {}},
1450
    pol1 = cli.CreateIPolicyFromOpts(minmax_ispecs=[{allowedv: {}}],
1425 1451
                                     std_ispecs=None,
1426 1452
                                     ipolicy_disk_templates=allowedv,
1427 1453
                                     ipolicy_vcpu_ratio=allowedv,
......
1448 1474
                           group_ipolicy, fill_all):
1449 1475
    exp_ipol = skel_exp_ipol.copy()
1450 1476
    if exp_minmax is not None:
1451
      minmax_ispecs = {}
1452
      for (key, spec) in exp_minmax.items():
1453
        minmax_ispecs[key] = self._ConvertSpecToStrings(spec)
1454
      exp_ipol[constants.ISPECS_MINMAX] = [exp_minmax]
1477
      minmax_ispecs = []
1478
      for exp_mm_pair in exp_minmax:
1479
        mmpair = {}
1480
        for (key, spec) in exp_mm_pair.items():
1481
          mmpair[key] = self._ConvertSpecToStrings(spec)
1482
        minmax_ispecs.append(mmpair)
1483
      exp_ipol[constants.ISPECS_MINMAX] = exp_minmax
1455 1484
    else:
1456 1485
      minmax_ispecs = None
1457 1486
    if exp_std is not None:
......
1463 1492
    self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1464 1493
                                 group_ipolicy, fill_all)
1465 1494
    if minmax_ispecs:
1466
      for (key, spec) in minmax_ispecs.items():
1467
        for par in [constants.ISPEC_MEM_SIZE, constants.ISPEC_DISK_SIZE]:
1468
          if par in spec:
1469
            spec[par] += "m"
1470
            self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1471
                                         group_ipolicy, fill_all)
1495
      for mmpair in minmax_ispecs:
1496
        for (key, spec) in mmpair.items():
1497
          for par in [constants.ISPEC_MEM_SIZE, constants.ISPEC_DISK_SIZE]:
1498
            if par in spec:
1499
              spec[par] += "m"
1500
              self._CheckNewStyleSpecsCall(exp_ipol, minmax_ispecs, std_ispecs,
1501
                                           group_ipolicy, fill_all)
1472 1502
    if std_ispecs:
1473 1503
      for par in [constants.ISPEC_MEM_SIZE, constants.ISPEC_DISK_SIZE]:
1474 1504
        if par in std_ispecs:
......
1477 1507
                                       group_ipolicy, fill_all)
1478 1508

  
1479 1509
  def testFullISpecs(self):
1480
    exp_minmax1 = {
1481
      constants.ISPECS_MIN: {
1482
        constants.ISPEC_MEM_SIZE: 512,
1483
        constants.ISPEC_CPU_COUNT: 2,
1484
        constants.ISPEC_DISK_COUNT: 2,
1485
        constants.ISPEC_DISK_SIZE: 512,
1486
        constants.ISPEC_NIC_COUNT: 2,
1487
        constants.ISPEC_SPINDLE_USE: 2,
1510
    exp_minmax1 = [
1511
      {
1512
        constants.ISPECS_MIN: {
1513
          constants.ISPEC_MEM_SIZE: 512,
1514
          constants.ISPEC_CPU_COUNT: 2,
1515
          constants.ISPEC_DISK_COUNT: 2,
1516
          constants.ISPEC_DISK_SIZE: 512,
1517
          constants.ISPEC_NIC_COUNT: 2,
1518
          constants.ISPEC_SPINDLE_USE: 2,
1519
          },
1520
        constants.ISPECS_MAX: {
1521
          constants.ISPEC_MEM_SIZE: 768*1024,
1522
          constants.ISPEC_CPU_COUNT: 7,
1523
          constants.ISPEC_DISK_COUNT: 6,
1524
          constants.ISPEC_DISK_SIZE: 2048*1024,
1525
          constants.ISPEC_NIC_COUNT: 3,
1526
          constants.ISPEC_SPINDLE_USE: 3,
1527
          },
1488 1528
        },
1489
      constants.ISPECS_MAX: {
1490
        constants.ISPEC_MEM_SIZE: 768*1024,
1491
        constants.ISPEC_CPU_COUNT: 7,
1492
        constants.ISPEC_DISK_COUNT: 6,
1493
        constants.ISPEC_DISK_SIZE: 2048*1024,
1494
        constants.ISPEC_NIC_COUNT: 3,
1495
        constants.ISPEC_SPINDLE_USE: 1,
1529
      ]
1530
    exp_minmax2 = [
1531
      {
1532
        constants.ISPECS_MIN: {
1533
          constants.ISPEC_MEM_SIZE: 512,
1534
          constants.ISPEC_CPU_COUNT: 2,
1535
          constants.ISPEC_DISK_COUNT: 2,
1536
          constants.ISPEC_DISK_SIZE: 512,
1537
          constants.ISPEC_NIC_COUNT: 2,
1538
          constants.ISPEC_SPINDLE_USE: 2,
1539
          },
1540
        constants.ISPECS_MAX: {
1541
          constants.ISPEC_MEM_SIZE: 768*1024,
1542
          constants.ISPEC_CPU_COUNT: 7,
1543
          constants.ISPEC_DISK_COUNT: 6,
1544
          constants.ISPEC_DISK_SIZE: 2048*1024,
1545
          constants.ISPEC_NIC_COUNT: 3,
1546
          constants.ISPEC_SPINDLE_USE: 3,
1547
          },
1496 1548
        },
1497
      }
1549
      {
1550
        constants.ISPECS_MIN: {
1551
          constants.ISPEC_MEM_SIZE: 1024*1024,
1552
          constants.ISPEC_CPU_COUNT: 3,
1553
          constants.ISPEC_DISK_COUNT: 3,
1554
          constants.ISPEC_DISK_SIZE: 256,
1555
          constants.ISPEC_NIC_COUNT: 4,
1556
          constants.ISPEC_SPINDLE_USE: 5,
1557
          },
1558
        constants.ISPECS_MAX: {
1559
          constants.ISPEC_MEM_SIZE: 2048*1024,
1560
          constants.ISPEC_CPU_COUNT: 5,
1561
          constants.ISPEC_DISK_COUNT: 5,
1562
          constants.ISPEC_DISK_SIZE: 1024*1024,
1563
          constants.ISPEC_NIC_COUNT: 5,
1564
          constants.ISPEC_SPINDLE_USE: 7,
1565
          },
1566
        },
1567
      ]
1498 1568
    exp_std1 = {
1499 1569
      constants.ISPEC_MEM_SIZE: 768*1024,
1500 1570
      constants.ISPEC_CPU_COUNT: 7,
......
1508 1578
        skel_ipolicy = constants.IPOLICY_DEFAULTS
1509 1579
      else:
1510 1580
        skel_ipolicy = {}
1511
      self._TestFullISpecsInner(skel_ipolicy, exp_minmax1, exp_std1,
1512
                                False, fill_all)
1513 1581
      self._TestFullISpecsInner(skel_ipolicy, None, exp_std1,
1514 1582
                                False, fill_all)
1515
      self._TestFullISpecsInner(skel_ipolicy, exp_minmax1, None,
1516
                                False, fill_all)
1583
      for exp_minmax in [exp_minmax1, exp_minmax2]:
1584
        self._TestFullISpecsInner(skel_ipolicy, exp_minmax, exp_std1,
1585
                                  False, fill_all)
1586
        self._TestFullISpecsInner(skel_ipolicy, exp_minmax, None,
1587
                                  False, fill_all)
1517 1588

  
1518 1589

  
1519 1590
class TestPrintIPolicyCommand(unittest.TestCase):
......
1528 1599
    "another_param": 101,
1529 1600
    }
1530 1601
  _SPECS2_STR = "another_param=101,param=10"
1602
  _SPECS3 = {
1603
    "par1": 1024,
1604
    "param": "abc",
1605
    }
1606
  _SPECS3_STR = "par1=1024,param=abc"
1531 1607

  
1532 1608
  def _CheckPrintIPolicyCommand(self, ipolicy, isgroup, expected):
1533 1609
    buf = StringIO()
......
1565 1641
        }]},
1566 1642
       " %s min:%s/max:%s" % (cli.IPOLICY_BOUNDS_SPECS_STR,
1567 1643
                              self._SPECS1_STR, self._SPECS2_STR)),
1644
      ({"minmax": [
1645
        {
1646
          "min": self._SPECS1,
1647
          "max": self._SPECS2,
1648
          },
1649
        {
1650
          "min": self._SPECS2,
1651
          "max": self._SPECS3,
1652
          },
1653
        ]},
1654
       " %s min:%s/max:%s//min:%s/max:%s" %
1655
       (cli.IPOLICY_BOUNDS_SPECS_STR, self._SPECS1_STR, self._SPECS2_STR,
1656
        self._SPECS2_STR, self._SPECS3_STR)),
1568 1657
      ]
1569 1658
    for (pol, exp) in cases:
1570 1659
      self._CheckPrintIPolicyCommand(pol, False, exp)

Also available in: Unified diff