Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / test / py / ganeti.objects_unittest.py
index 704e767..97594f8 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/python
 #
 
-# Copyright (C) 2006, 2007, 2008, 2010, 2012 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2010, 2012, 2013 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -22,6 +22,7 @@
 """Script for unittesting the objects module"""
 
 
+import copy
 import unittest
 
 from ganeti import constants
@@ -92,23 +93,24 @@ class TestClusterObject(unittest.TestCase):
     self.failUnlessEqual(cl.GetHVDefaults(constants.HT_FAKE),
                          cl.hvparams[constants.HT_FAKE])
     self.failUnlessEqual(cl.GetHVDefaults(None), {})
-    self.failUnlessEqual(cl.GetHVDefaults(constants.HT_XEN_PVM,
-                                          os_name="lenny-image"),
-                         cl.os_hvp["lenny-image"][constants.HT_XEN_PVM])
-
+    defaults = cl.GetHVDefaults(constants.HT_XEN_PVM,
+                                          os_name="lenny-image")
+    for param, value in cl.os_hvp["lenny-image"][constants.HT_XEN_PVM].items():
+      self.assertEqual(value, defaults[param])
 
   def testFillHvFullMerge(self):
     inst_hvparams = {
       "blah": "blubb",
       }
 
-    fake_dict = {
+    fake_dict = constants.HVC_DEFAULTS[constants.HT_FAKE].copy()
+    fake_dict.update({
       "foo": "baz",
       "bar": "foo",
       "foobar": "foobar",
       "blah": "blubb",
       "blubb": "blah",
-      }
+      })
     fake_inst = objects.Instance(name="foobar",
                                  os="lenny-image",
                                  hypervisor=constants.HT_FAKE,
@@ -131,14 +133,19 @@ class TestClusterObject(unittest.TestCase):
                                  os="ubuntu-hardy",
                                  hypervisor=constants.HT_XEN_PVM,
                                  hvparams=inst_hvparams)
-    self.assertEqual(inst_hvparams, self.fake_cl.FillHV(fake_inst))
+    filled_conf = self.fake_cl.FillHV(fake_inst)
+    for param, value in constants.HVC_DEFAULTS[constants.HT_XEN_PVM].items():
+      if param == "blah":
+        value = "blubb"
+      self.assertEqual(value, filled_conf[param])
 
-  def testFillHvEmptyParams(self):
+  def testFillHvDefaultParams(self):
     fake_inst = objects.Instance(name="foobar",
                                  os="ubuntu-hardy",
                                  hypervisor=constants.HT_XEN_PVM,
                                  hvparams={})
-    self.assertEqual({}, self.fake_cl.FillHV(fake_inst))
+    self.assertEqual(constants.HVC_DEFAULTS[constants.HT_XEN_PVM],
+                     self.fake_cl.FillHV(fake_inst))
 
   def testFillHvPartialParams(self):
     os = "lenny-image"
@@ -146,8 +153,9 @@ class TestClusterObject(unittest.TestCase):
                                  os=os,
                                  hypervisor=constants.HT_XEN_PVM,
                                  hvparams={})
-    self.assertEqual(self.fake_cl.os_hvp[os][constants.HT_XEN_PVM],
-                     self.fake_cl.FillHV(fake_inst))
+    filled_conf = self.fake_cl.FillHV(fake_inst)
+    for param, value in self.fake_cl.os_hvp[os][constants.HT_XEN_PVM].items():
+      self.assertEqual(value, filled_conf[param])
 
   def testFillNdParamsCluster(self):
     fake_node = objects.Node(name="test",
@@ -222,6 +230,64 @@ class TestClusterObject(unittest.TestCase):
     cluster = objects.Cluster(ipolicy={"unknown_key": None})
     self.assertRaises(errors.ConfigurationError, cluster.UpgradeConfig)
 
+  def testUpgradeEnabledDiskTemplates(self):
+    cfg = objects.ConfigData()
+    cfg.cluster = objects.Cluster()
+    cfg.cluster.volume_group_name = "myvg"
+    instance1 = objects.Instance()
+    instance1.disk_template = constants.DT_DISKLESS
+    instance2 = objects.Instance()
+    instance2.disk_template = constants.DT_RBD
+    cfg.instances = { "myinstance1": instance1, "myinstance2": instance2 }
+    nodegroup = objects.NodeGroup()
+    nodegroup.ipolicy = {}
+    nodegroup.ipolicy[constants.IPOLICY_DTS] = [instance1.disk_template, \
+      constants.DT_BLOCK]
+    cfg.cluster.ipolicy = {}
+    cfg.cluster.ipolicy[constants.IPOLICY_DTS] = \
+      [constants.DT_EXT, constants.DT_DISKLESS]
+    cfg.nodegroups = { "mynodegroup": nodegroup }
+    cfg._UpgradeEnabledDiskTemplates()
+    expected_disk_templates = [constants.DT_DRBD8,
+                               constants.DT_PLAIN,
+                               instance1.disk_template,
+                               instance2.disk_template]
+    self.assertEqual(set(expected_disk_templates),
+                     set(cfg.cluster.enabled_disk_templates))
+    self.assertEqual(set([instance1.disk_template]),
+                     set(cfg.cluster.ipolicy[constants.IPOLICY_DTS]))
+
+
+class TestClusterObjectTcpUdpPortPool(unittest.TestCase):
+  def testNewCluster(self):
+    self.assertTrue(objects.Cluster().tcpudp_port_pool is None)
+
+  def testSerializingEmpty(self):
+    self.assertEqual(objects.Cluster().ToDict(), {
+      "tcpudp_port_pool": [],
+      })
+
+  def testSerializing(self):
+    cluster = objects.Cluster.FromDict({})
+    self.assertEqual(cluster.tcpudp_port_pool, set())
+
+    cluster.tcpudp_port_pool.add(3546)
+    cluster.tcpudp_port_pool.add(62511)
+
+    data = cluster.ToDict()
+    self.assertEqual(data.keys(), ["tcpudp_port_pool"])
+    self.assertEqual(sorted(data["tcpudp_port_pool"]), sorted([3546, 62511]))
+
+  def testDeserializingEmpty(self):
+    cluster = objects.Cluster.FromDict({})
+    self.assertEqual(cluster.tcpudp_port_pool, set())
+
+  def testDeserialize(self):
+    cluster = objects.Cluster.FromDict({
+      "tcpudp_port_pool": [26214, 10039, 267],
+      })
+    self.assertEqual(cluster.tcpudp_port_pool, set([26214, 10039, 267]))
+
 
 class TestOS(unittest.TestCase):
   ALL_DATA = [
@@ -266,9 +332,9 @@ class TestInstance(unittest.TestCase):
     inst = objects.Instance(name="fakeinstplain.example.com",
       primary_node="node3.example.com",
       disks=[
-        objects.Disk(dev_type=constants.LD_LV, size=128,
+        objects.Disk(dev_type=constants.DT_PLAIN, size=128,
                      logical_id=("myxenvg", "disk25494")),
-        objects.Disk(dev_type=constants.LD_LV, size=512,
+        objects.Disk(dev_type=constants.DT_PLAIN, size=512,
                      logical_id=("myxenvg", "disk29071")),
         ])
 
@@ -283,13 +349,13 @@ class TestInstance(unittest.TestCase):
     inst = objects.Instance(name="fakeinstdrbd.example.com",
       primary_node="node10.example.com",
       disks=[
-        objects.Disk(dev_type=constants.LD_DRBD8, size=786432,
+        objects.Disk(dev_type=constants.DT_DRBD8, size=786432,
           logical_id=("node10.example.com", "node15.example.com",
                       12300, 0, 0, "secret"),
           children=[
-            objects.Disk(dev_type=constants.LD_LV, size=786432,
+            objects.Disk(dev_type=constants.DT_PLAIN, size=786432,
                          logical_id=("myxenvg", "disk0")),
-            objects.Disk(dev_type=constants.LD_LV, size=128,
+            objects.Disk(dev_type=constants.DT_PLAIN, size=128,
                          logical_id=("myxenvg", "meta0"))
           ],
           iv_name="disk/0")
@@ -336,7 +402,7 @@ class TestNode(unittest.TestCase):
 
   def testDiskState(self):
     node = objects.Node(name="node32087.example.com", disk_state={
-      constants.LD_LV: {
+      constants.DT_PLAIN: {
         "lv32352": objects.NodeDiskState(total=128),
         "lv2082": objects.NodeDiskState(total=512),
         },
@@ -349,14 +415,12 @@ class TestNode(unittest.TestCase):
 
     self.assertEqual(node2.name, "node32087.example.com")
     self.assertEqual(frozenset(node2.disk_state), frozenset([
-      constants.LD_LV,
-      ]))
-    self.assertEqual(frozenset(node2.disk_state[constants.LD_LV]), frozenset([
-      "lv32352",
-      "lv2082",
+      constants.DT_PLAIN,
       ]))
-    self.assertEqual(node2.disk_state[constants.LD_LV]["lv2082"].total, 512)
-    self.assertEqual(node2.disk_state[constants.LD_LV]["lv32352"].total, 128)
+    self.assertEqual(frozenset(node2.disk_state[constants.DT_PLAIN]),
+                     frozenset(["lv32352", "lv2082"]))
+    self.assertEqual(node2.disk_state[constants.DT_PLAIN]["lv2082"].total, 512)
+    self.assertEqual(node2.disk_state[constants.DT_PLAIN]["lv32352"].total, 128)
 
   def testFilterEsNdp(self):
     node1 = objects.Node(name="node11673.example.com", ndparams={
@@ -383,15 +447,232 @@ class TestInstancePolicy(unittest.TestCase):
 
   def _AssertIPolicyIsFull(self, policy):
     self.assertEqual(frozenset(policy.keys()), constants.IPOLICY_ALL_KEYS)
-    for key in constants.IPOLICY_ISPECS:
-      spec = policy[key]
-      self.assertEqual(frozenset(spec.keys()), constants.ISPECS_PARAMETERS)
+    self.assertTrue(len(policy[constants.ISPECS_MINMAX]) > 0)
+    for minmax in policy[constants.ISPECS_MINMAX]:
+      self.assertEqual(frozenset(minmax.keys()), constants.ISPECS_MINMAX_KEYS)
+      for key in constants.ISPECS_MINMAX_KEYS:
+        self.assertEqual(frozenset(minmax[key].keys()),
+                         constants.ISPECS_PARAMETERS)
+    self.assertEqual(frozenset(policy[constants.ISPECS_STD].keys()),
+                     constants.ISPECS_PARAMETERS)
 
   def testDefaultIPolicy(self):
     objects.InstancePolicy.CheckParameterSyntax(constants.IPOLICY_DEFAULTS,
                                                 True)
     self._AssertIPolicyIsFull(constants.IPOLICY_DEFAULTS)
 
+  def _AssertPolicyIsBad(self, ipolicy, do_check_std=None):
+    if do_check_std is None:
+      check_std_vals = [False, True]
+    else:
+      check_std_vals = [do_check_std]
+    for check_std in check_std_vals:
+      self.assertRaises(errors.ConfigurationError,
+                        objects.InstancePolicy.CheckISpecSyntax,
+                        ipolicy, check_std)
+
+  def testCheckISpecSyntax(self):
+    default_stdspec = constants.IPOLICY_DEFAULTS[constants.ISPECS_STD]
+    incomplete_ipolicies = [
+      {
+         constants.ISPECS_MINMAX: [],
+         constants.ISPECS_STD: default_stdspec,
+         },
+      {
+         constants.ISPECS_MINMAX: [{}],
+         constants.ISPECS_STD: default_stdspec,
+         },
+      {
+        constants.ISPECS_MINMAX: [{
+          constants.ISPECS_MIN: NotImplemented,
+          }],
+        constants.ISPECS_STD: default_stdspec,
+        },
+      {
+        constants.ISPECS_MINMAX: [{
+          constants.ISPECS_MAX: NotImplemented,
+          }],
+        constants.ISPECS_STD: default_stdspec,
+        },
+      {
+        constants.ISPECS_MINMAX: [{
+          constants.ISPECS_MIN: NotImplemented,
+          constants.ISPECS_MAX: NotImplemented,
+          }],
+        },
+      ]
+    for ipol in incomplete_ipolicies:
+      self.assertRaises(errors.ConfigurationError,
+                        objects.InstancePolicy.CheckISpecSyntax,
+                        ipol, True)
+      oldminmax = ipol[constants.ISPECS_MINMAX]
+      if oldminmax:
+        # Prepending valid specs shouldn't change the error
+        ipol[constants.ISPECS_MINMAX] = ([constants.ISPECS_MINMAX_DEFAULTS] +
+                                         oldminmax)
+        self.assertRaises(errors.ConfigurationError,
+                          objects.InstancePolicy.CheckISpecSyntax,
+                          ipol, True)
+
+    good_ipolicy = {
+      constants.ISPECS_MINMAX: [
+        {
+          constants.ISPECS_MIN: {
+            constants.ISPEC_MEM_SIZE: 64,
+            constants.ISPEC_CPU_COUNT: 1,
+            constants.ISPEC_DISK_COUNT: 2,
+            constants.ISPEC_DISK_SIZE: 64,
+            constants.ISPEC_NIC_COUNT: 1,
+            constants.ISPEC_SPINDLE_USE: 1,
+            },
+          constants.ISPECS_MAX: {
+            constants.ISPEC_MEM_SIZE: 16384,
+            constants.ISPEC_CPU_COUNT: 5,
+            constants.ISPEC_DISK_COUNT: 12,
+            constants.ISPEC_DISK_SIZE: 1024,
+            constants.ISPEC_NIC_COUNT: 9,
+            constants.ISPEC_SPINDLE_USE: 18,
+            },
+          },
+        {
+          constants.ISPECS_MIN: {
+            constants.ISPEC_MEM_SIZE: 32768,
+            constants.ISPEC_CPU_COUNT: 8,
+            constants.ISPEC_DISK_COUNT: 1,
+            constants.ISPEC_DISK_SIZE: 1024,
+            constants.ISPEC_NIC_COUNT: 1,
+            constants.ISPEC_SPINDLE_USE: 1,
+            },
+          constants.ISPECS_MAX: {
+            constants.ISPEC_MEM_SIZE: 65536,
+            constants.ISPEC_CPU_COUNT: 10,
+            constants.ISPEC_DISK_COUNT: 5,
+            constants.ISPEC_DISK_SIZE: 1024 * 1024,
+            constants.ISPEC_NIC_COUNT: 3,
+            constants.ISPEC_SPINDLE_USE: 12,
+            },
+          },
+        ],
+      }
+    good_ipolicy[constants.ISPECS_STD] = copy.deepcopy(
+      good_ipolicy[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX])
+    # Check that it's really good before making it bad
+    objects.InstancePolicy.CheckISpecSyntax(good_ipolicy, True)
+
+    bad_ipolicy = copy.deepcopy(good_ipolicy)
+    for minmax in bad_ipolicy[constants.ISPECS_MINMAX]:
+      for (key, spec) in minmax.items():
+        for param in spec:
+          oldv = spec[param]
+          del spec[param]
+          self._AssertPolicyIsBad(bad_ipolicy)
+          if key == constants.ISPECS_MIN:
+            spec[param] = minmax[constants.ISPECS_MAX][param] + 1
+          self._AssertPolicyIsBad(bad_ipolicy)
+          spec[param] = oldv
+    assert bad_ipolicy == good_ipolicy
+
+    stdspec = bad_ipolicy[constants.ISPECS_STD]
+    for param in stdspec:
+      oldv = stdspec[param]
+      del stdspec[param]
+      self._AssertPolicyIsBad(bad_ipolicy, True)
+      # Note that std spec is the same as a max spec
+      stdspec[param] = oldv + 1
+      self._AssertPolicyIsBad(bad_ipolicy, True)
+      stdspec[param] = oldv
+    assert bad_ipolicy == good_ipolicy
+
+    for minmax in good_ipolicy[constants.ISPECS_MINMAX]:
+      for spec in minmax.values():
+        good_ipolicy[constants.ISPECS_STD] = spec
+        objects.InstancePolicy.CheckISpecSyntax(good_ipolicy, True)
+
+  def testCheckISpecParamSyntax(self):
+    par = "my_parameter"
+    for check_std in [True, False]:
+      # Min and max only
+      good_values = [(11, 11), (11, 40), (0, 0)]
+      for (mn, mx) in good_values:
+        minmax = dict((k, {}) for k in constants.ISPECS_MINMAX_KEYS)
+        minmax[constants.ISPECS_MIN][par] = mn
+        minmax[constants.ISPECS_MAX][par] = mx
+        objects.InstancePolicy._CheckISpecParamSyntax(minmax, {}, par,
+                                                     check_std)
+      minmax = dict((k, {}) for k in constants.ISPECS_MINMAX_KEYS)
+      minmax[constants.ISPECS_MIN][par] = 11
+      minmax[constants.ISPECS_MAX][par] = 5
+      self.assertRaises(errors.ConfigurationError,
+                        objects.InstancePolicy._CheckISpecParamSyntax,
+                        minmax, {}, par, check_std)
+    # Min, std, max
+    good_values = [
+      (11, 11, 11),
+      (11, 11, 40),
+      (11, 40, 40),
+      ]
+    for (mn, st, mx) in good_values:
+      minmax = {
+        constants.ISPECS_MIN: {par: mn},
+        constants.ISPECS_MAX: {par: mx},
+        }
+      stdspec = {par: st}
+      objects.InstancePolicy._CheckISpecParamSyntax(minmax, stdspec, par, True)
+    bad_values = [
+      (11, 11,  5, True),
+      (40, 11, 11, True),
+      (11, 80, 40, False),
+      (11,  5, 40, False,),
+      (11,  5,  5, True),
+      (40, 40, 11, True),
+      ]
+    for (mn, st, mx, excp) in bad_values:
+      minmax = {
+        constants.ISPECS_MIN: {par: mn},
+        constants.ISPECS_MAX: {par: mx},
+        }
+      stdspec = {par: st}
+      if excp:
+        self.assertRaises(errors.ConfigurationError,
+                          objects.InstancePolicy._CheckISpecParamSyntax,
+                          minmax, stdspec, par, True)
+      else:
+        ret = objects.InstancePolicy._CheckISpecParamSyntax(minmax, stdspec,
+                                                            par, True)
+        self.assertFalse(ret)
+
+  def testCheckDiskTemplates(self):
+    invalid = "this_is_not_a_good_template"
+    for dt in constants.DISK_TEMPLATES:
+      objects.InstancePolicy.CheckDiskTemplates([dt])
+    objects.InstancePolicy.CheckDiskTemplates(list(constants.DISK_TEMPLATES))
+    bad_examples = [
+      [invalid],
+      [constants.DT_DRBD8, invalid],
+      list(constants.DISK_TEMPLATES) + [invalid],
+      [],
+      None,
+      ]
+    for dtl in bad_examples:
+      self.assertRaises(errors.ConfigurationError,
+                        objects.InstancePolicy.CheckDiskTemplates,
+                        dtl)
+
+  def testCheckParameterSyntax(self):
+    invalid = "this_key_shouldnt_be_here"
+    for check_std in [True, False]:
+      objects.InstancePolicy.CheckParameterSyntax({}, check_std)
+      policy = {invalid: None}
+      self.assertRaises(errors.ConfigurationError,
+                        objects.InstancePolicy.CheckParameterSyntax,
+                        policy, check_std)
+      for par in constants.IPOLICY_PARAMETERS:
+        for val in ("blah", None, {}, [42]):
+          policy = {par: val}
+          self.assertRaises(errors.ConfigurationError,
+                            objects.InstancePolicy.CheckParameterSyntax,
+                            policy, check_std)
+
   def testFillIPolicyEmpty(self):
     policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, {})
     objects.InstancePolicy.CheckParameterSyntax(policy, True)
@@ -407,7 +688,7 @@ class TestInstancePolicy(unittest.TestCase):
   def _AssertIPolicyMerged(self, default_pol, diff_pol, merged_pol):
     for (key, value) in merged_pol.items():
       if key in diff_pol:
-        if key in constants.IPOLICY_ISPECS:
+        if key == constants.ISPECS_STD:
           self._AssertISpecsMerged(default_pol[key], diff_pol[key], value)
         else:
           self.assertEqual(value, diff_pol[key])
@@ -418,8 +699,10 @@ class TestInstancePolicy(unittest.TestCase):
     partial_policies = [
       {constants.IPOLICY_VCPU_RATIO: 3.14},
       {constants.IPOLICY_SPINDLE_RATIO: 2.72},
-      {constants.IPOLICY_DTS: []},
       {constants.IPOLICY_DTS: [constants.DT_FILE]},
+      {constants.ISPECS_STD: {constants.ISPEC_DISK_COUNT: 3}},
+      {constants.ISPECS_MINMAX: [constants.ISPECS_MINMAX_DEFAULTS,
+                                 constants.ISPECS_MINMAX_DEFAULTS]}
       ]
     for diff_pol in partial_policies:
       policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
@@ -427,22 +710,44 @@ class TestInstancePolicy(unittest.TestCase):
       self._AssertIPolicyIsFull(policy)
       self._AssertIPolicyMerged(constants.IPOLICY_DEFAULTS, diff_pol, policy)
 
-  def testFillIPolicySpecs(self):
-    partial_policies = [
-      {constants.ISPECS_MIN: {constants.ISPEC_MEM_SIZE: 32},
-       constants.ISPECS_MAX: {constants.ISPEC_CPU_COUNT: 1024}},
-      {constants.ISPECS_STD: {constants.ISPEC_DISK_SIZE: 2048},
-       constants.ISPECS_MAX: {
-          constants.ISPEC_DISK_COUNT: constants.MAX_DISKS - 1,
-          constants.ISPEC_NIC_COUNT: constants.MAX_NICS - 1,
-          }},
-      {constants.ISPECS_STD: {constants.ISPEC_SPINDLE_USE: 3}},
-      ]
-    for diff_pol in partial_policies:
-      policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
-      objects.InstancePolicy.CheckParameterSyntax(policy, True)
-      self._AssertIPolicyIsFull(policy)
-      self._AssertIPolicyMerged(constants.IPOLICY_DEFAULTS, diff_pol, policy)
+  def testFillIPolicyKeepsUnknown(self):
+    INVALID_KEY = "invalid_ipolicy_key"
+    diff_pol = {
+      INVALID_KEY: None,
+      }
+    policy = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, diff_pol)
+    self.assertTrue(INVALID_KEY in policy)
+
+
+class TestDisk(unittest.TestCase):
+  def addChild(self, disk):
+    """Adds a child of the same device type as the parent."""
+    disk.children = []
+    child = objects.Disk()
+    child.dev_type = disk.dev_type
+    disk.children.append(child)
+
+  def testUpgradeConfigDevTypeLegacy(self):
+    for old, new in [("drbd8", constants.DT_DRBD8),
+                     ("lvm", constants.DT_PLAIN)]:
+      disk = objects.Disk()
+      disk.dev_type = old
+      self.addChild(disk)
+      disk.UpgradeConfig()
+      self.assertEqual(new, disk.dev_type)
+      self.assertEqual(new, disk.children[0].dev_type)
+
+  def testUpgradeConfigDevTypeLegacyUnchanged(self):
+    dev_types = [constants.DT_FILE, constants.DT_SHARED_FILE,
+                 constants.DT_BLOCK, constants.DT_EXT,
+                 constants.DT_RBD]
+    for dev_type in dev_types:
+      disk = objects.Disk()
+      disk.dev_type = dev_type
+      self.addChild(disk)
+      disk.UpgradeConfig()
+      self.assertEqual(dev_type, disk.dev_type)
+      self.assertEqual(dev_type, disk.children[0].dev_type)
 
 
 if __name__ == "__main__":