cmdlib: Adding helper for instance policy
authorRené Nussbaumer <rn@google.com>
Tue, 13 Dec 2011 08:38:47 +0000 (09:38 +0100)
committerRené Nussbaumer <rn@google.com>
Fri, 6 Jan 2012 12:45:59 +0000 (13:45 +0100)
Signed-off-by: René Nussbaumer <rn@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>

lib/cmdlib.py
test/ganeti.cmdlib_unittest.py

index 973f978..8591e8f 100644 (file)
@@ -1036,6 +1036,127 @@ def _CheckMinMaxSpecs(name, ipolicy, value):
   return None
 
 
+def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
+                                 nic_count, disk_sizes,
+                                 _check_spec_fn=_CheckMinMaxSpecs):
+  """Verifies ipolicy against provided specs.
+
+  @type ipolicy: dict
+  @param ipolicy: The ipolicy
+  @type mem_size: int
+  @param mem_size: The memory size
+  @type cpu_count: int
+  @param cpu_count: Used cpu cores
+  @type disk_count: int
+  @param disk_count: Number of disks used
+  @type nic_count: int
+  @param nic_count: Number of nics used
+  @type disk_sizes: list of ints
+  @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
+  @param _check_spec_fn: The checking function (unittest only)
+  @return: A list of violations, or an empty list of no violations are found
+
+  """
+  assert disk_count == len(disk_sizes)
+
+  test_settings = [
+    (constants.ISPEC_MEM_SIZE, mem_size),
+    (constants.ISPEC_CPU_COUNT, cpu_count),
+    (constants.ISPEC_DISK_COUNT, disk_count),
+    (constants.ISPEC_NIC_COUNT, nic_count),
+    ] + map((lambda d: (constants.ISPEC_DISK_SIZE, d)), disk_sizes)
+
+  return filter(None,
+                (_check_spec_fn(name, ipolicy, value)
+                 for (name, value) in test_settings))
+
+
+def _ComputeIPolicyInstanceViolation(ipolicy, instance,
+                                     _compute_fn=_ComputeIPolicySpecViolation):
+  """Compute if instance meets the specs of ipolicy.
+
+  @type ipolicy: dict
+  @param ipolicy: The ipolicy to verify against
+  @type instance: L{objects.Instance}
+  @param instance: The instance to verify
+  @param _compute_fn: The function to verify ipolicy (unittest only)
+  @see: L{_ComputeIPolicySpecViolation}
+
+  """
+  mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
+  cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
+  disk_count = len(instance.disks)
+  disk_sizes = [disk.size for disk in instance.disks]
+  nic_count = len(instance.nics)
+
+  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
+                     disk_sizes)
+
+
+def _ComputeIPolicyInstanceSpecViolation(ipolicy, instance_spec,
+    _compute_fn=_ComputeIPolicySpecViolation):
+  """Compute if instance specs meets the specs of ipolicy.
+
+  @type ipolicy: dict
+  @param ipolicy: The ipolicy to verify against
+  @param instance_spec: dict
+  @param instance_spec: The instance spec to verify
+  @param _compute_fn: The function to verify ipolicy (unittest only)
+  @see: L{_ComputeIPolicySpecViolation}
+
+  """
+  mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
+  cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
+  disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
+  disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
+  nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
+
+  return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
+                     disk_sizes)
+
+
+def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
+                                 target_group,
+                                 _compute_fn=_ComputeIPolicyInstanceViolation):
+  """Compute if instance meets the specs of the new target group.
+
+  @param ipolicy: The ipolicy to verify
+  @param instance: The instance object to verify
+  @param current_group: The current group of the instance
+  @param target_group: The new group of the instance
+  @param _compute_fn: The function to verify ipolicy (unittest only)
+  @see: L{_ComputeIPolicySpecViolation}
+
+  """
+  if current_group == target_group:
+    return []
+  else:
+    return _compute_fn(ipolicy, instance)
+
+
+def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
+                            _compute_fn=_ComputeIPolicyNodeViolation):
+  """Checks that the target node is correct in terms of instance policy.
+
+  @param ipolicy: The ipolicy to verify
+  @param instance: The instance object to verify
+  @param node: The new node to relocate
+  @param ignore: Ignore violations of the ipolicy
+  @param _compute_fn: The function to verify ipolicy (unittest only)
+  @see: L{_ComputeIPolicySpecViolation}
+
+  """
+  res = _compute_fn(ipolicy, instance, instance.primary_node.group, node.group)
+
+  if res:
+    msg = ("Instance does not meet target node group's (%s) instance"
+           " policy: %s") % (node.group, utils.CommaJoin(res))
+    if ignore:
+      lu.LogWarning(msg)
+    else:
+      raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
+
+
 def _ExpandItemName(fn, name, kind):
   """Expand an item name.
 
@@ -1249,11 +1370,10 @@ def _DecideSelfPromotion(lu, exceptions=None):
   return mc_now < mc_should
 
 
-def _CalculateGroupIPolicy(cfg, group):
+def _CalculateGroupIPolicy(cluster, group):
   """Calculate instance policy for group.
 
   """
-  cluster = cfg.GetClusterInfo()
   return cluster.SimpleFillIPolicy(group.ipolicy)
 
 
index 0f90a95..a5419b3 100755 (executable)
@@ -569,5 +569,147 @@ class TestDiskStateHelper(unittest.TestCase):
                       new, None)
 
 
+def _ValidateCheckMinMaxSpec(name, *_):
+  assert name in constants.ISPECS_PARAMETERS
+  return None
+
+
+class _SpecWrapper:
+  def __init__(self, spec):
+    self.spec = spec
+
+  def CheckMinMaxSpec(self, *args):
+    return self.spec.pop(0)
+
+
+class TestComputeIPolicySpecViolation(unittest.TestCase):
+  def test(self):
+    check_fn = _ValidateCheckMinMaxSpec
+    ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
+                                              [1024], _check_spec_fn=check_fn)
+    self.assertEqual(ret, [])
+
+  def testInvalidArguments(self):
+    self.assertRaises(AssertionError, cmdlib._ComputeIPolicySpecViolation,
+                      NotImplemented, 1024, 1, 1, 1, [])
+
+  def testInvalidSpec(self):
+    spec = _SpecWrapper([None, False, "foo", None, "bar"])
+    check_fn = spec.CheckMinMaxSpec
+    ret = cmdlib._ComputeIPolicySpecViolation(NotImplemented, 1024, 1, 1, 1,
+                                              [1024], _check_spec_fn=check_fn)
+    self.assertEqual(ret, ["foo", "bar"])
+    self.assertFalse(spec.spec)
+
+
+class _StubComputeIPolicySpecViolation:
+  def __init__(self, mem_size, cpu_count, disk_count, nic_count, disk_sizes):
+    self.mem_size = mem_size
+    self.cpu_count = cpu_count
+    self.disk_count = disk_count
+    self.nic_count = nic_count
+    self.disk_sizes = disk_sizes
+
+  def __call__(self, _, mem_size, cpu_count, disk_count, nic_count, disk_sizes):
+    assert self.mem_size == mem_size
+    assert self.cpu_count == cpu_count
+    assert self.disk_count == disk_count
+    assert self.nic_count == nic_count
+    assert self.disk_sizes == disk_sizes
+
+    return []
+
+
+class TestComputeIPolicyInstanceViolation(unittest.TestCase):
+  def test(self):
+    beparams = {
+      constants.BE_MAXMEM: 2048,
+      constants.BE_VCPUS: 2,
+      }
+    disks = [objects.Disk(size=512)]
+    instance = objects.Instance(beparams=beparams, disks=disks, nics=[])
+    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512])
+    ret = cmdlib._ComputeIPolicyInstanceViolation(NotImplemented, instance,
+                                                  _compute_fn=stub)
+    self.assertEqual(ret, [])
+
+
+class TestComputeIPolicyInstanceSpecViolation(unittest.TestCase):
+  def test(self):
+    ispec = {
+      constants.ISPEC_MEM_SIZE: 2048,
+      constants.ISPEC_CPU_COUNT: 2,
+      constants.ISPEC_DISK_COUNT: 1,
+      constants.ISPEC_DISK_SIZE: [512],
+      constants.ISPEC_NIC_COUNT: 0,
+      }
+    stub = _StubComputeIPolicySpecViolation(2048, 2, 1, 0, [512])
+    ret = cmdlib._ComputeIPolicyInstanceSpecViolation(NotImplemented, ispec,
+                                                      _compute_fn=stub)
+    self.assertEqual(ret, [])
+
+
+class _CallRecorder:
+  def __init__(self, return_value=None):
+    self.called = False
+    self.return_value = return_value
+
+  def __call__(self, *args):
+    self.called = True
+    return self.return_value
+
+
+class TestComputeIPolicyNodeViolation(unittest.TestCase):
+  def setUp(self):
+    self.recorder = _CallRecorder(return_value=[])
+
+  def testSameGroup(self):
+    ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
+                                              "foo", "foo",
+                                              _compute_fn=self.recorder)
+    self.assertFalse(self.recorder.called)
+    self.assertEqual(ret, [])
+
+  def testDifferentGroup(self):
+    ret = cmdlib._ComputeIPolicyNodeViolation(NotImplemented, NotImplemented,
+                                              "foo", "bar",
+                                              _compute_fn=self.recorder)
+    self.assertTrue(self.recorder.called)
+    self.assertEqual(ret, [])
+
+
+class TestCheckTargetNodeIPolicy(unittest.TestCase):
+  def setUp(self):
+    self.instance = objects.Instance(primary_node=objects.Node(group="foo"))
+    self.target_node = objects.Node(group="bar")
+    self.lu = _FakeLU()
+
+  def testNoViolation(self):
+    compute_recoder = _CallRecorder(return_value=[])
+    cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
+                                   self.target_node,
+                                   _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    self.assertEqual(self.lu.warning_log, [])
+
+  def testNoIgnore(self):
+    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
+    self.assertRaises(errors.OpPrereqError, cmdlib._CheckTargetNodeIPolicy,
+                      self.lu, NotImplemented, self.instance, self.target_node,
+                      _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    self.assertEqual(self.lu.warning_log, [])
+
+  def testIgnoreViolation(self):
+    compute_recoder = _CallRecorder(return_value=["mem_size not in range"])
+    cmdlib._CheckTargetNodeIPolicy(self.lu, NotImplemented, self.instance,
+                                   self.target_node, ignore=True,
+                                   _compute_fn=compute_recoder)
+    self.assertTrue(compute_recoder.called)
+    msg = ("Instance does not meet target node group's (bar) instance policy:"
+           " mem_size not in range")
+    self.assertEqual(self.lu.warning_log, [(msg, ())])
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()