4 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script for unittesting the config module"""
30 from ganeti import bootstrap
31 from ganeti import config
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import objects
35 from ganeti import utils
36 from ganeti import netutils
37 from ganeti import compat
38 from ganeti.cmdlib import instance
40 from ganeti.config import TemporaryReservationManager
47 def _StubGetEntResolver():
48 return mocks.FakeGetentResolver()
51 class TestConfigRunner(unittest.TestCase):
52 """Testing case for HooksRunner"""
54 fd, self.cfg_file = tempfile.mkstemp()
56 self._init_cluster(self.cfg_file)
60 os.unlink(self.cfg_file)
64 def _get_object(self):
65 """Returns an instance of ConfigWriter"""
66 cfg = config.ConfigWriter(cfg_file=self.cfg_file, offline=True,
67 _getents=_StubGetEntResolver)
70 def _init_cluster(self, cfg):
71 """Initializes the cfg object"""
72 me = netutils.Hostname()
73 ip = constants.IP4_ADDRESS_LOCALHOST
74 # master_ip must not conflict with the node ip address
75 master_ip = "127.0.0.2"
77 cluster_config = objects.Cluster(
81 highest_used_port=(constants.FIRST_DRBD_PORT - 1),
82 mac_prefix="aa:00:00",
83 volume_group_name="xenvg",
84 drbd_usermode_helper="/bin/true",
85 nicparams={constants.PP_DEFAULT: constants.NICC_DEFAULTS},
86 ndparams=constants.NDC_DEFAULTS,
87 tcpudp_port_pool=set(),
88 enabled_hypervisors=[constants.HT_FAKE],
91 master_netdev=constants.DEFAULT_BRIDGE,
92 cluster_name="cluster.local",
93 file_storage_dir="/tmp",
97 master_node_config = objects.Node(name=me.name,
101 master_candidate=True)
103 bootstrap.InitConfig(constants.CONFIG_VERSION,
104 cluster_config, master_node_config, self.cfg_file)
106 def _create_instance(self):
107 """Create and return an instance object"""
108 inst = objects.Instance(name="test.example.com",
111 disk_template=constants.DT_DISKLESS,
112 primary_node=self._get_object().GetMasterNode())
116 """Test instantiate config object"""
120 """Test initialize the config file"""
121 cfg = self._get_object()
122 self.failUnlessEqual(1, len(cfg.GetNodeList()))
123 self.failUnlessEqual(0, len(cfg.GetInstanceList()))
125 def testUpdateCluster(self):
126 """Test updates on the cluster object"""
127 cfg = self._get_object()
128 # construct a fake cluster object
129 fake_cl = objects.Cluster()
130 # fail if we didn't read the config
131 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
133 cl = cfg.GetClusterInfo()
134 # first pass, must not fail
136 # second pass, also must not fail (after the config has been written)
138 # but the fake_cl update should still fail
139 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
141 def testUpdateNode(self):
142 """Test updates on one node object"""
143 cfg = self._get_object()
144 # construct a fake node
145 fake_node = objects.Node()
146 # fail if we didn't read the config
147 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
150 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
151 # first pass, must not fail
152 cfg.Update(node, None)
153 # second pass, also must not fail (after the config has been written)
154 cfg.Update(node, None)
155 # but the fake_node update should still fail
156 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
159 def testUpdateInstance(self):
160 """Test updates on one instance object"""
161 cfg = self._get_object()
162 # construct a fake instance
163 inst = self._create_instance()
164 fake_instance = objects.Instance()
165 # fail if we didn't read the config
166 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
169 cfg.AddInstance(inst, "my-job")
170 instance = cfg.GetInstanceInfo(cfg.GetInstanceList()[0])
171 # first pass, must not fail
172 cfg.Update(instance, None)
173 # second pass, also must not fail (after the config has been written)
174 cfg.Update(instance, None)
175 # but the fake_instance update should still fail
176 self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
179 def testUpgradeSave(self):
180 """Test that any modification done during upgrading is saved back"""
181 cfg = self._get_object()
183 # Remove an element, run upgrade, and check if the element is
184 # back and the file upgraded
185 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
186 # For a ConfigObject, None is the same as a missing field
188 oldsaved = utils.ReadFile(self.cfg_file)
190 self.assertTrue(node.ndparams is not None)
191 newsaved = utils.ReadFile(self.cfg_file)
192 # We rely on the fact that at least the serial number changes
193 self.assertNotEqual(oldsaved, newsaved)
195 # Add something that should not be there this time
196 key = list(constants.NDC_GLOBALS)[0]
197 node.ndparams[key] = constants.NDC_DEFAULTS[key]
198 cfg._WriteConfig(None)
199 oldsaved = utils.ReadFile(self.cfg_file)
201 self.assertTrue(node.ndparams.get(key) is None)
202 newsaved = utils.ReadFile(self.cfg_file)
203 self.assertNotEqual(oldsaved, newsaved)
205 # Do the upgrade again, this time there should be no update
208 newsaved = utils.ReadFile(self.cfg_file)
209 self.assertEqual(oldsaved, newsaved)
211 # Reload the configuration again: it shouldn't change the file
214 newsaved = utils.ReadFile(self.cfg_file)
215 self.assertEqual(oldsaved, newsaved)
217 def testNICParameterSyntaxCheck(self):
218 """Test the NIC's CheckParameterSyntax function"""
219 mode = constants.NIC_MODE
220 link = constants.NIC_LINK
221 m_bridged = constants.NIC_MODE_BRIDGED
222 m_routed = constants.NIC_MODE_ROUTED
223 CheckSyntax = objects.NIC.CheckParameterSyntax
225 CheckSyntax(constants.NICC_DEFAULTS)
226 CheckSyntax({mode: m_bridged, link: "br1"})
227 CheckSyntax({mode: m_routed, link: "default"})
228 self.assertRaises(errors.ConfigurationError,
229 CheckSyntax, {mode: "000invalid", link: "any"})
230 self.assertRaises(errors.ConfigurationError,
231 CheckSyntax, {mode: m_bridged, link: None})
232 self.assertRaises(errors.ConfigurationError,
233 CheckSyntax, {mode: m_bridged, link: ""})
235 def testGetNdParamsDefault(self):
236 cfg = self._get_object()
237 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
238 self.assertEqual(cfg.GetNdParams(node), constants.NDC_DEFAULTS)
240 def testGetNdParamsModifiedNode(self):
242 constants.ND_OOB_PROGRAM: "/bin/node-oob",
243 constants.ND_SPINDLE_COUNT: 1,
244 constants.ND_EXCLUSIVE_STORAGE: False,
247 cfg = self._get_object()
248 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
249 node.ndparams = my_ndparams
250 cfg.Update(node, None)
251 self.assertEqual(cfg.GetNdParams(node), my_ndparams)
253 def testGetNdParamsInheritance(self):
255 constants.ND_OOB_PROGRAM: "/bin/node-oob",
258 constants.ND_SPINDLE_COUNT: 10,
260 expected_ndparams = {
261 constants.ND_OOB_PROGRAM: "/bin/node-oob",
262 constants.ND_SPINDLE_COUNT: 10,
263 constants.ND_EXCLUSIVE_STORAGE:
264 constants.NDC_DEFAULTS[constants.ND_EXCLUSIVE_STORAGE],
266 cfg = self._get_object()
267 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
268 node.ndparams = node_ndparams
269 cfg.Update(node, None)
270 group = cfg.GetNodeGroup(node.group)
271 group.ndparams = group_ndparams
272 cfg.Update(group, None)
273 self.assertEqual(cfg.GetNdParams(node), expected_ndparams)
275 def testAddGroupFillsFieldsIfMissing(self):
276 cfg = self._get_object()
277 group = objects.NodeGroup(name="test", members=[])
278 cfg.AddNodeGroup(group, "my-job")
279 self.assert_(utils.UUID_RE.match(group.uuid))
280 self.assertEqual(constants.ALLOC_POLICY_PREFERRED, group.alloc_policy)
282 def testAddGroupPreservesFields(self):
283 cfg = self._get_object()
284 group = objects.NodeGroup(name="test", members=[],
285 alloc_policy=constants.ALLOC_POLICY_LAST_RESORT)
286 cfg.AddNodeGroup(group, "my-job")
287 self.assertEqual(constants.ALLOC_POLICY_LAST_RESORT, group.alloc_policy)
289 def testAddGroupDoesNotPreserveFields(self):
290 cfg = self._get_object()
291 group = objects.NodeGroup(name="test", members=[],
292 serial_no=17, ctime=123, mtime=456)
293 cfg.AddNodeGroup(group, "my-job")
294 self.assertEqual(1, group.serial_no)
295 self.assert_(group.ctime > 1200000000)
296 self.assert_(group.mtime > 1200000000)
298 def testAddGroupCanSkipUUIDCheck(self):
299 cfg = self._get_object()
300 uuid = cfg.GenerateUniqueID("my-job")
301 group = objects.NodeGroup(name="test", members=[], uuid=uuid,
302 serial_no=17, ctime=123, mtime=456)
304 self.assertRaises(errors.ConfigurationError,
305 cfg.AddNodeGroup, group, "my-job")
307 cfg.AddNodeGroup(group, "my-job", check_uuid=False) # Does not raise.
308 self.assertEqual(uuid, group.uuid)
310 def testAssignGroupNodes(self):
311 me = netutils.Hostname()
312 cfg = self._get_object()
315 grp1 = objects.NodeGroup(name="grp1", members=[],
316 uuid="2f2fadf7-2a70-4a23-9ab5-2568c252032c")
318 cfg.AddNodeGroup(grp1, "job")
320 grp2 = objects.NodeGroup(name="grp2", members=[],
321 uuid="798d0de3-680f-4a0e-b29a-0f54f693b3f1")
323 cfg.AddNodeGroup(grp2, "job")
324 self.assertEqual(set(map(operator.attrgetter("name"),
325 cfg.GetAllNodeGroupsInfo().values())),
326 set(["grp1", "grp2", constants.INITIAL_NODE_GROUP_NAME]))
329 cluster_serial = cfg.GetClusterInfo().serial_no
330 cfg.AssignGroupNodes([])
334 node1 = objects.Node(name="node1", group=grp1.uuid, ndparams={},
337 node2 = objects.Node(name="node2", group=grp2.uuid, ndparams={},
340 cfg.AddNode(node1, "job")
341 cfg.AddNode(node2, "job")
343 self.assertEqual(set(cfg.GetNodeList()),
344 set(["node1-uuid", "node2-uuid",
345 cfg.GetNodeInfoByName(me.name).uuid]))
347 def _VerifySerials():
348 self.assertEqual(cfg.GetClusterInfo().serial_no, cluster_serial)
349 self.assertEqual(node1.serial_no, node1_serial)
350 self.assertEqual(node2.serial_no, node2_serial)
351 self.assertEqual(grp1.serial_no, grp1_serial)
352 self.assertEqual(grp2.serial_no, grp2_serial)
356 self.assertEqual(set(grp1.members), set(["node1-uuid"]))
357 self.assertEqual(set(grp2.members), set(["node2-uuid"]))
359 # Check invalid nodes and groups
360 self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
361 ("unknown.node.example.com", grp2.uuid),
363 self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
364 (node1.name, "unknown-uuid"),
367 self.assertEqual(node1.group, grp1.uuid)
368 self.assertEqual(node2.group, grp2.uuid)
369 self.assertEqual(set(grp1.members), set(["node1-uuid"]))
370 self.assertEqual(set(grp2.members), set(["node2-uuid"]))
373 cfg.AssignGroupNodes([])
377 # Assign to the same group (should be a no-op)
378 self.assertEqual(node2.group, grp2.uuid)
379 cfg.AssignGroupNodes([
380 (node2.uuid, grp2.uuid),
383 self.assertEqual(node2.group, grp2.uuid)
385 self.assertEqual(set(grp1.members), set(["node1-uuid"]))
386 self.assertEqual(set(grp2.members), set(["node2-uuid"]))
388 # Assign node 2 to group 1
389 self.assertEqual(node2.group, grp2.uuid)
390 cfg.AssignGroupNodes([
391 (node2.uuid, grp1.uuid),
397 self.assertEqual(node2.group, grp1.uuid)
399 self.assertEqual(set(grp1.members), set(["node1-uuid", "node2-uuid"]))
400 self.assertFalse(grp2.members)
402 # And assign both nodes to group 2
403 self.assertEqual(node1.group, grp1.uuid)
404 self.assertEqual(node2.group, grp1.uuid)
405 self.assertNotEqual(grp1.uuid, grp2.uuid)
406 cfg.AssignGroupNodes([
407 (node1.uuid, grp2.uuid),
408 (node2.uuid, grp2.uuid),
415 self.assertEqual(node1.group, grp2.uuid)
416 self.assertEqual(node2.group, grp2.uuid)
418 self.assertFalse(grp1.members)
419 self.assertEqual(set(grp2.members), set(["node1-uuid", "node2-uuid"]))
422 orig_group = node2.group
424 other_uuid = "68b3d087-6ea5-491c-b81f-0a47d90228c5"
425 assert compat.all(node.group != other_uuid
426 for node in cfg.GetAllNodesInfo().values())
427 node2.group = "68b3d087-6ea5-491c-b81f-0a47d90228c5"
428 self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
429 (node2.uuid, grp2.uuid),
433 node2.group = orig_group
435 def _TestVerifyConfigIPolicy(self, ipolicy, ipowner, cfg, isgroup):
436 INVALID_KEY = "this_key_cannot_exist"
438 ipolicy[INVALID_KEY] = None
439 # A call to cluster.SimpleFillIPolicy causes different kinds of error
440 # depending on the owner (cluster or group)
442 errs = cfg.VerifyConfig()
443 self.assertTrue(len(errs) >= 1)
444 errstr = "%s has invalid instance policy" % ipowner
445 self.assertTrue(_IsErrorInList(errstr, errs))
447 self.assertRaises(AssertionError, cfg.VerifyConfig)
448 del ipolicy[INVALID_KEY]
449 errs = cfg.VerifyConfig()
450 self.assertFalse(errs)
452 key = list(constants.IPOLICY_PARAMETERS)[0]
453 hasoldv = (key in ipolicy)
456 ipolicy[key] = "blah"
457 errs = cfg.VerifyConfig()
458 self.assertTrue(len(errs) >= 1)
459 self.assertTrue(_IsErrorInList("%s has invalid instance policy" % ipowner,
467 if constants.ISPECS_MINMAX in ipolicy:
468 for k in range(len(ipolicy[constants.ISPECS_MINMAX])):
470 (ipolicy[constants.ISPECS_MINMAX][k][constants.ISPECS_MIN],
471 "%s[%s]/%s" % (constants.ISPECS_MINMAX, k, constants.ISPECS_MIN)),
472 (ipolicy[constants.ISPECS_MINMAX][k][constants.ISPECS_MAX],
473 "%s[%s]/%s" % (constants.ISPECS_MINMAX, k, constants.ISPECS_MAX)),
475 if constants.ISPECS_STD in ipolicy:
476 ispeclist.append((ipolicy[constants.ISPECS_STD], constants.ISPECS_STD))
478 for (ispec, ispecpath) in ispeclist:
479 ispec[INVALID_KEY] = None
480 errs = cfg.VerifyConfig()
481 self.assertTrue(len(errs) >= 1)
482 self.assertTrue(_IsErrorInList(("%s has invalid ipolicy/%s" %
483 (ipowner, ispecpath)), errs))
484 del ispec[INVALID_KEY]
485 errs = cfg.VerifyConfig()
486 self.assertFalse(errs)
488 for par in constants.ISPECS_PARAMETERS:
489 hasoldv = par in ispec
493 errs = cfg.VerifyConfig()
494 self.assertTrue(len(errs) >= 1)
495 self.assertTrue(_IsErrorInList(("%s has invalid ipolicy/%s" %
496 (ipowner, ispecpath)), errs))
501 errs = cfg.VerifyConfig()
502 self.assertFalse(errs)
504 if constants.ISPECS_MINMAX in ipolicy:
505 # Test partial minmax specs
506 for minmax in ipolicy[constants.ISPECS_MINMAX]:
507 for key in constants.ISPECS_MINMAX_KEYS:
508 self.assertTrue(key in minmax)
511 errs = cfg.VerifyConfig()
512 self.assertTrue(len(errs) >= 1)
513 self.assertTrue(_IsErrorInList("Missing instance specification",
516 for par in constants.ISPECS_PARAMETERS:
519 errs = cfg.VerifyConfig()
520 self.assertTrue(len(errs) >= 1)
521 self.assertTrue(_IsErrorInList("Missing instance specs parameters",
524 errs = cfg.VerifyConfig()
525 self.assertFalse(errs)
527 def _TestVerifyConfigGroupIPolicy(self, groupinfo, cfg):
528 old_ipolicy = groupinfo.ipolicy
529 ipolicy = cfg.GetClusterInfo().SimpleFillIPolicy({})
530 groupinfo.ipolicy = ipolicy
531 # Test partial policies
532 for key in constants.IPOLICY_ALL_KEYS:
533 self.assertTrue(key in ipolicy)
536 errs = cfg.VerifyConfig()
537 self.assertFalse(errs)
539 groupinfo.ipolicy = old_ipolicy
541 def _TestVerifyConfigClusterIPolicy(self, ipolicy, cfg):
542 # Test partial policies
543 for key in constants.IPOLICY_ALL_KEYS:
544 self.assertTrue(key in ipolicy)
547 self.assertRaises(AssertionError, cfg.VerifyConfig)
549 errs = cfg.VerifyConfig()
550 self.assertFalse(errs)
551 # Partial standard specs
552 ispec = ipolicy[constants.ISPECS_STD]
553 for par in constants.ISPECS_PARAMETERS:
556 errs = cfg.VerifyConfig()
557 self.assertTrue(len(errs) >= 1)
558 self.assertTrue(_IsErrorInList("Missing instance specs parameters",
561 errs = cfg.VerifyConfig()
562 self.assertFalse(errs)
564 def testVerifyConfig(self):
565 cfg = self._get_object()
567 errs = cfg.VerifyConfig()
568 self.assertFalse(errs)
570 node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
571 key = list(constants.NDC_GLOBALS)[0]
572 node.ndparams[key] = constants.NDC_DEFAULTS[key]
573 errs = cfg.VerifyConfig()
574 self.assertTrue(len(errs) >= 1)
575 self.assertTrue(_IsErrorInList("has some global parameters set", errs))
577 del node.ndparams[key]
578 errs = cfg.VerifyConfig()
579 self.assertFalse(errs)
581 cluster = cfg.GetClusterInfo()
582 nodegroup = cfg.GetNodeGroup(cfg.GetNodeGroupList()[0])
583 self._TestVerifyConfigIPolicy(cluster.ipolicy, "cluster", cfg, False)
584 self._TestVerifyConfigClusterIPolicy(cluster.ipolicy, cfg)
585 self._TestVerifyConfigIPolicy(nodegroup.ipolicy, nodegroup.name, cfg, True)
586 self._TestVerifyConfigGroupIPolicy(nodegroup, cfg)
587 nodegroup.ipolicy = cluster.SimpleFillIPolicy(nodegroup.ipolicy)
588 self._TestVerifyConfigIPolicy(nodegroup.ipolicy, nodegroup.name, cfg, True)
590 # Tests for Ssconf helper functions
591 def testUnlockedGetHvparamsString(self):
592 hvparams = {"a": "A", "b": "B", "c": "C"}
594 cfg_writer = self._get_object()
595 cfg_writer._config_data = mock.Mock()
596 cfg_writer._config_data.cluster = mock.Mock()
597 cfg_writer._config_data.cluster.hvparams = {hvname: hvparams}
599 result = cfg_writer._UnlockedGetHvparamsString(hvname)
601 self.assertTrue("a=A" in result)
602 lines = [line for line in result.split('\n') if line != '']
603 self.assertEqual(len(hvparams.keys()), len(lines))
605 def testExtendByAllHvparamsStrings(self):
606 all_hvparams = {constants.HT_XEN_PVM: "foo"}
608 cfg_writer = self._get_object()
610 cfg_writer._ExtendByAllHvparamsStrings(ssconf_values, all_hvparams)
612 expected_key = constants.SS_HVPARAMS_PREF + constants.HT_XEN_PVM
613 self.assertTrue(expected_key in ssconf_values)
616 def _IsErrorInList(err_str, err_list):
617 return any(map(lambda e: err_str in e, err_list))
620 class TestTRM(unittest.TestCase):
624 t = TemporaryReservationManager()
625 t.Reserve(self.EC_ID, "a")
626 self.assertFalse(t.Reserved(self.EC_ID))
627 self.assertTrue(t.Reserved("a"))
628 self.assertEqual(len(t.GetReserved()), 1)
630 def testDuplicate(self):
631 t = TemporaryReservationManager()
632 t.Reserve(self.EC_ID, "a")
633 self.assertRaises(errors.ReservationError, t.Reserve, 2, "a")
634 t.DropECReservations(self.EC_ID)
635 self.assertFalse(t.Reserved("a"))
638 class TestCheckInstanceDiskIvNames(unittest.TestCase):
640 def _MakeDisks(names):
641 return [objects.Disk(iv_name=name) for name in names]
643 def testNoError(self):
644 disks = self._MakeDisks(["disk/0", "disk/1"])
645 self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
646 instance._UpdateIvNames(0, disks)
647 self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
649 def testWrongNames(self):
650 disks = self._MakeDisks(["disk/1", "disk/3", "disk/2"])
651 self.assertEqual(config._CheckInstanceDiskIvNames(disks), [
652 (0, "disk/0", "disk/1"),
653 (1, "disk/1", "disk/3"),
657 instance._UpdateIvNames(0, disks)
658 self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
661 if __name__ == "__main__":
662 testutils.GanetiTestProgram()