Revision ec996117

b/qa/ganeti-qa.py
523 523

  
524 524

  
525 525
def _BuildSpecDict(par, mn, st, mx):
526
  return {par: {"min": mn, "std": st, "max": mx}}
526
  return {
527
    "min": {par: mn},
528
    "max": {par: mx},
529
    "std": {par: st},
530
    }
527 531

  
528 532

  
529 533
def TestIPolicyPlainInstance():
......
534 538
    return
535 539

  
536 540
  # This test assumes that the group policy is empty
537
  (_, old_specs) = qa_cluster.TestClusterSetISpecs({})
541
  (_, old_specs) = qa_cluster.TestClusterSetISpecs()
538 542
  node = qa_config.AcquireNode()
539 543
  try:
540
    # Log of policy changes, list of tuples: (change, policy_violated)
544
    # Log of policy changes, list of tuples:
545
    # (full_change, incremental_change, policy_violated)
541 546
    history = []
542 547
    instance = qa_instance.TestInstanceAddWithPlainDisk([node])
543 548
    try:
......
547 552
        (iminval, imaxval) = qa_instance.GetInstanceSpec(instance.name, par)
548 553
        # Some specs must be multiple of 4
549 554
        new_spec = _BuildSpecDict(par, imaxval + 4, imaxval + 4, imaxval + 4)
550
        history.append((new_spec, True))
551
        qa_cluster.TestClusterSetISpecs(new_spec)
555
        history.append((None, new_spec, True))
556
        qa_cluster.TestClusterSetISpecs(diff_specs=new_spec)
552 557
        qa_cluster.AssertClusterVerify(warnings=policyerror)
553 558
        if iminval > 0:
554 559
          # Some specs must be multiple of 4
......
557 562
          else:
558 563
            upper = iminval - 1
559 564
          new_spec = _BuildSpecDict(par, 0, upper, upper)
560
          history.append((new_spec, True))
561
          qa_cluster.TestClusterSetISpecs(new_spec)
565
          history.append((None, new_spec, True))
566
          qa_cluster.TestClusterSetISpecs(diff_specs=new_spec)
562 567
          qa_cluster.AssertClusterVerify(warnings=policyerror)
563
        qa_cluster.TestClusterSetISpecs(old_specs)
564
        history.append((old_specs, False))
568
        qa_cluster.TestClusterSetISpecs(new_specs=old_specs)
569
        history.append((old_specs, None, False))
565 570
      qa_instance.TestInstanceRemove(instance)
566 571
    finally:
567 572
      instance.Release()
568 573

  
569 574
    # Now we replay the same policy changes, and we expect that the instance
570 575
    # cannot be created for the cases where we had a policy violation above
571
    for (change, failed) in history:
572
      qa_cluster.TestClusterSetISpecs(change)
576
    for (new_specs, diff_specs, failed) in history:
577
      qa_cluster.TestClusterSetISpecs(new_specs=new_specs,
578
                                      diff_specs=diff_specs)
573 579
      if failed:
574 580
        qa_instance.TestInstanceAddWithPlainDisk([node], fail=True)
575 581
      # Instance creation with no policy violation has been tested already
b/qa/qa_cluster.py
532 532
  @rtype: tuple
533 533
  @return: (policy, specs), where:
534 534
      - policy is a dictionary of the policy values, instance specs excluded
535
      - specs is dict of dict, specs[par][key] is a spec value, where key is
535
      - specs is dict of dict, specs[key][par] is a spec value, where key is
536 536
        "min", "max", or "std"
537 537

  
538 538
  """
......
541 541
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
542 542

  
543 543
  # Sanity checks
544
  assert len(ret_specs) > 0
545
  good = all("min" in d and "std" in d and "max" in d
546
             for d in ret_specs.values())
547
  assert good, "Missing item in specs: %s" % ret_specs
544
  assert "min" in ret_specs and "std" in ret_specs and "max" in ret_specs
548 545
  assert len(ret_policy) > 0
549 546
  return (ret_policy, ret_specs)
550 547

  
......
606 603
      AssertEqual(eff_policy[p], old_policy[p])
607 604

  
608 605

  
609
def TestClusterSetISpecs(new_specs, fail=False, old_values=None):
606
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
607
                         old_values=None):
610 608
  """Change instance specs.
611 609

  
612
  @type new_specs: dict of dict
613
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
614
      can be an empty dictionary.
610
  At most one of new_specs or diff_specs can be specified.
611

  
612
  @type new_specs: dict
613
  @param new_specs: new complete specs, in the same format returned by
614
      L{_GetClusterIPolicy}
615
  @type diff_specs: dict
616
  @param diff_specs: diff_specs[key][par], where key is "min", "max", "std". It
617
      can be an incomplete specifications or an empty dictionary.
615 618
  @type fail: bool
616 619
  @param fail: if the change is expected to fail
617 620
  @type old_values: tuple
618 621
  @param old_values: (old_policy, old_specs), as returned by
619
     L{_GetClusterIPolicy}
622
      L{_GetClusterIPolicy}
620 623
  @return: same as L{_GetClusterIPolicy}
621 624

  
622 625
  """
623 626
  build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
624
  return qa_utils.TestSetISpecs(new_specs, get_policy_fn=_GetClusterIPolicy,
625
                                build_cmd_fn=build_cmd, fail=fail,
626
                                old_values=old_values)
627
  return qa_utils.TestSetISpecs(
628
    new_specs=new_specs, diff_specs=diff_specs,
629
    get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
630
    fail=fail, old_values=old_values)
627 631

  
628 632

  
629 633
def TestClusterModifyISpecs():
......
646 650
      (False, 0, 4, "a"),
647 651
      # This is to restore the old values
648 652
      (True,
649
       cur_specs[par]["min"], cur_specs[par]["std"], cur_specs[par]["max"])
653
       cur_specs["min"][par], cur_specs["std"][par], cur_specs["max"][par])
650 654
      ]
651 655
    for (good, mn, st, mx) in test_values:
652
      new_vals = {par: {"min": str(mn), "std": str(st), "max": str(mx)}}
656
      new_vals = {
657
        "min": {par: mn},
658
        "std": {par: st},
659
        "max": {par: mx}
660
        }
653 661
      cur_state = (cur_policy, cur_specs)
654 662
      # We update cur_specs, as we've copied the values to restore already
655
      (cur_policy, cur_specs) = TestClusterSetISpecs(new_vals, fail=not good,
656
                                                     old_values=cur_state)
663
      (cur_policy, cur_specs) = TestClusterSetISpecs(
664
        diff_specs=new_vals, fail=not good, old_values=cur_state)
657 665

  
658 666
    # Get the ipolicy command
659 667
    mnode = qa_config.GetMasterNode()
b/qa/qa_group.py
87 87
  @rtype: tuple
88 88
  @return: (policy, specs), where:
89 89
      - policy is a dictionary of the policy values, instance specs excluded
90
      - specs is dict of dict, specs[par][key] is a spec value, where key is
90
      - specs is dict of dict, specs[key][par] is a spec value, where key is
91 91
        "min" or "max"
92 92

  
93 93
  """
......
98 98
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
99 99

  
100 100
  # Sanity checks
101
  assert len(ret_specs) > 0
102
  good = all("min" in d and "max" in d
103
             for d in ret_specs.values())
104
  assert good, "Missing item in specs: %s" % ret_specs
101
  assert "min" in ret_specs and "max" in ret_specs
105 102
  assert len(ret_policy) > 0
106 103
  return (ret_policy, ret_specs)
107 104

  
108 105

  
109
def _TestGroupSetISpecs(groupname, new_specs, fail=False, old_values=None):
106
def _TestGroupSetISpecs(groupname, new_specs=None, diff_specs=None,
107
                        fail=False, old_values=None):
110 108
  """Change instance specs on a group.
111 109

  
110
  At most one of new_specs or diff_specs can be specified.
111

  
112 112
  @type groupname: string
113 113
  @param groupname: group name
114
  @type new_specs: dict of dict
115
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
116
      can be an empty dictionary.
114
  @type new_specs: dict
115
  @param new_specs: new complete specs, in the same format returned by
116
      L{_GetGroupIPolicy}
117
  @type diff_specs: dict
118
  @param diff_specs: diff_specs[key][par], where key is "min", "max". It
119
      can be an incomplete specifications or an empty dictionary.
117 120
  @type fail: bool
118 121
  @param fail: if the change is expected to fail
119 122
  @type old_values: tuple
120 123
  @param old_values: (old_policy, old_specs), as returned by
121
     L{_GetGroupIPolicy}
124
      L{_GetGroupIPolicy}
122 125
  @return: same as L{_GetGroupIPolicy}
123 126

  
124 127
  """
125 128
  build_cmd = lambda opts: ["gnt-group", "modify"] + opts + [groupname]
126 129
  get_policy = lambda: _GetGroupIPolicy(groupname)
127
  return qa_utils.TestSetISpecs(new_specs, get_policy_fn=get_policy,
128
                                build_cmd_fn=build_cmd, fail=fail,
129
                                old_values=old_values)
130
  return qa_utils.TestSetISpecs(
131
    new_specs=new_specs, diff_specs=diff_specs,
132
    get_policy_fn=get_policy, build_cmd_fn=build_cmd,
133
    fail=fail, old_values=old_values)
130 134

  
131 135

  
132 136
def _TestGroupModifyISpecs(groupname):
133 137
  # This test is built on the assumption that the default ipolicy holds for
134 138
  # the node group under test
135 139
  old_values = _GetGroupIPolicy(groupname)
136
  mod_values = _TestGroupSetISpecs(groupname,
137
                                   dict((p, {"min": 4, "max": 4})
138
                                        for p in constants.ISPECS_PARAMETERS),
140
  samevals = dict((p, 4) for p in constants.ISPECS_PARAMETERS)
141
  base_specs = {"min": samevals, "max": samevals}
142
  mod_values = _TestGroupSetISpecs(groupname, new_specs=base_specs,
139 143
                                   old_values=old_values)
140 144
  for par in constants.ISPECS_PARAMETERS:
141 145
    # First make sure that the test works with good values
142
    mod_values = _TestGroupSetISpecs(groupname, {par: {"min": 8, "max": 8}},
146
    good_specs = {"min": {par: 8}, "max": {par: 8}}
147
    mod_values = _TestGroupSetISpecs(groupname, diff_specs=good_specs,
143 148
                                     old_values=mod_values)
144
    _TestGroupSetISpecs(groupname, {par: {"min": 8, "max": 4}},
145
                        fail=True, old_values=mod_values)
149
    bad_specs = {"min": {par: 8}, "max": {par: 4}}
150
    _TestGroupSetISpecs(groupname, diff_specs=bad_specs, fail=True,
151
                        old_values=mod_values)
146 152
  AssertCommand(["gnt-group", "modify", "--ipolicy-bounds-specs", "default",
147 153
                 groupname])
148 154
  AssertEqual(_GetGroupIPolicy(groupname), old_values)
b/qa/qa_utils.py
23 23

  
24 24
"""
25 25

  
26
import copy
26 27
import operator
27 28
import os
28 29
import random
......
779 780
    return path
780 781

  
781 782

  
782
def _GetParameterOptions(key, specs, old_specs):
783
def _GetParameterOptions(specs):
783 784
  """Helper to build policy options."""
784
  values = ["%s=%s" % (par, keyvals[key])
785
            for (par, keyvals) in specs.items()
786
            if key in keyvals]
787
  if old_specs:
788
    present_pars = frozenset(par
789
                             for (par, keyvals) in specs.items()
790
                             if key in keyvals)
791
    values.extend("%s=%s" % (par, keyvals[key])
792
                  for (par, keyvals) in old_specs.items()
793
                  if key in keyvals and par not in present_pars)
785
  values = ["%s=%s" % (par, val)
786
            for (par, val) in specs.items()]
794 787
  return ",".join(values)
795 788

  
796 789

  
797
def TestSetISpecs(new_specs, get_policy_fn=None, build_cmd_fn=None,
798
                  fail=False, old_values=None):
790
def TestSetISpecs(new_specs=None, diff_specs=None, get_policy_fn=None,
791
                  build_cmd_fn=None, fail=False, old_values=None):
799 792
  """Change instance specs for an object.
800 793

  
801
  @type new_specs: dict of dict
802
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
803
      can be an empty dictionary.
794
  At most one of new_specs or diff_specs can be specified.
795

  
796
  @type new_specs: dict
797
  @param new_specs: new complete specs, in the same format returned by
798
      L{ParseIPolicy}.
799
  @type diff_specs: dict
800
  @param diff_specs: diff_specs[key][par], where key is "min", "max", "std". It
801
      can be an incomplete specifications or an empty dictionary.
804 802
  @type get_policy_fn: function
805 803
  @param get_policy_fn: function that returns the current policy as in
806
      L{qa_cluster._GetClusterIPolicy}
804
      L{ParseIPolicy}
807 805
  @type build_cmd_fn: function
808 806
  @param build_cmd_fn: function that return the full command line from the
809 807
      options alone
......
811 809
  @param fail: if the change is expected to fail
812 810
  @type old_values: tuple
813 811
  @param old_values: (old_policy, old_specs), as returned by
814
     L{qa_cluster._GetClusterIPolicy}
815
  @return: same as L{qa_cluster._GetClusterIPolicy}
812
     L{ParseIPolicy}
813
  @return: same as L{ParseIPolicy}
816 814

  
817 815
  """
818 816
  assert get_policy_fn is not None
819 817
  assert build_cmd_fn is not None
818
  assert new_specs is None or diff_specs is None
820 819

  
821 820
  if old_values:
822 821
    (old_policy, old_specs) = old_values
823 822
  else:
824 823
    (old_policy, old_specs) = get_policy_fn()
824

  
825
  if diff_specs:
826
    new_specs = copy.deepcopy(old_specs)
827
    for (key, parvals) in diff_specs.items():
828
      for (par, val) in parvals.items():
829
        new_specs[key][par] = val
830

  
825 831
  if new_specs:
826 832
    cmd = []
827
    if any(("min" in val or "max" in val) for val in new_specs.values()):
833
    if (diff_specs is None or
834
        ("min" in diff_specs or "max" in diff_specs)):
828 835
      minmax_opt_items = []
829 836
      for key in ["min", "max"]:
830
        keyopt = _GetParameterOptions(key, new_specs, old_specs)
837
        keyopt = _GetParameterOptions(new_specs[key])
831 838
        minmax_opt_items.append("%s:%s" % (key, keyopt))
832 839
      cmd.extend([
833 840
        "--ipolicy-bounds-specs",
834 841
        "/".join(minmax_opt_items)
835 842
        ])
836
    std_opt = _GetParameterOptions("std", new_specs, {})
843
    if diff_specs:
844
      std_source = diff_specs
845
    else:
846
      std_source = new_specs
847
    std_opt = _GetParameterOptions(std_source.get("std", {}))
837 848
    if std_opt:
838 849
      cmd.extend(["--ipolicy-std-specs", std_opt])
839 850
    AssertCommand(build_cmd_fn(cmd), fail=fail)
840 851

  
841
  # Check the new state
842
  (eff_policy, eff_specs) = get_policy_fn()
843
  AssertEqual(eff_policy, old_policy)
844
  if fail:
845
    AssertEqual(eff_specs, old_specs)
852
    # Check the new state
853
    (eff_policy, eff_specs) = get_policy_fn()
854
    AssertEqual(eff_policy, old_policy)
855
    if fail:
856
      AssertEqual(eff_specs, old_specs)
857
    else:
858
      AssertEqual(eff_specs, new_specs)
859

  
846 860
  else:
847
    for par in eff_specs:
848
      for key in eff_specs[par]:
849
        if par in new_specs and key in new_specs[par]:
850
          AssertEqual(int(eff_specs[par][key]), int(new_specs[par][key]))
851
        else:
852
          AssertEqual(int(eff_specs[par][key]), int(old_specs[par][key]))
861
    (eff_policy, eff_specs) = (old_policy, old_specs)
862

  
853 863
  return (eff_policy, eff_specs)
854 864

  
855 865

  
......
861 871
  @rtype: tuple
862 872
  @return: (policy, specs), where:
863 873
      - policy is a dictionary of the policy values, instance specs excluded
864
      - specs is dict of dict, specs[par][key] is a spec value, where key is
874
      - specs is dict of dict, specs[key][par] is a spec value, where key is
865 875
        "min", "max", or "std"
866 876

  
867 877
  """
......
870 880
  ispec_keys = constants.ISPECS_MINMAX_KEYS | frozenset([constants.ISPECS_STD])
871 881
  for (key, val) in policy.items():
872 882
    if key in ispec_keys:
873
      for (par, pval) in val.items():
874
        d = ret_specs.setdefault(par, {})
875
        d[key] = pval
883
      ret_specs[key] = val
876 884
    else:
877 885
      ret_policy[key] = val
878 886
  return (ret_policy, ret_specs)

Also available in: Unified diff