Simplify logic in Node operations
[ganeti-local] / test / ganeti.config_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Script for unittesting the config module"""
23
24
25 import unittest
26 import os
27 import time
28 import tempfile
29 import os.path
30 import socket
31 import operator
32 import itertools
33
34 from ganeti import bootstrap
35 from ganeti import config
36 from ganeti import constants
37 from ganeti import errors
38 from ganeti import objects
39 from ganeti import utils
40 from ganeti import netutils
41 from ganeti import compat
42 from ganeti import cmdlib
43
44 from ganeti.config import TemporaryReservationManager
45
46 import testutils
47 import mocks
48
49
50 def _StubGetEntResolver():
51   return mocks.FakeGetentResolver()
52
53
54 class TestConfigRunner(unittest.TestCase):
55   """Testing case for HooksRunner"""
56   def setUp(self):
57     fd, self.cfg_file = tempfile.mkstemp()
58     os.close(fd)
59     self._init_cluster(self.cfg_file)
60
61   def tearDown(self):
62     try:
63       os.unlink(self.cfg_file)
64     except OSError:
65       pass
66
67   def _get_object(self):
68     """Returns a instance of ConfigWriter"""
69     cfg = config.ConfigWriter(cfg_file=self.cfg_file, offline=True,
70                               _getents=_StubGetEntResolver)
71     return cfg
72
73   def _init_cluster(self, cfg):
74     """Initializes the cfg object"""
75     me = netutils.Hostname()
76     ip = constants.IP4_ADDRESS_LOCALHOST
77
78     cluster_config = objects.Cluster(
79       serial_no=1,
80       rsahostkeypub="",
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],
89       master_node=me.name,
90       master_ip="127.0.0.1",
91       master_netdev=constants.DEFAULT_BRIDGE,
92       cluster_name="cluster.local",
93       file_storage_dir="/tmp",
94       uid_pool=[],
95       )
96
97     master_node_config = objects.Node(name=me.name,
98                                       primary_ip=me.ip,
99                                       secondary_ip=ip,
100                                       serial_no=1,
101                                       master_candidate=True)
102
103     bootstrap.InitConfig(constants.CONFIG_VERSION,
104                          cluster_config, master_node_config, self.cfg_file)
105
106   def _create_instance(self):
107     """Create and return an instance object"""
108     inst = objects.Instance(name="test.example.com", disks=[], nics=[],
109                             disk_template=constants.DT_DISKLESS,
110                             primary_node=self._get_object().GetMasterNode())
111     return inst
112
113   def testEmpty(self):
114     """Test instantiate config object"""
115     self._get_object()
116
117   def testInit(self):
118     """Test initialize the config file"""
119     cfg = self._get_object()
120     self.failUnlessEqual(1, len(cfg.GetNodeList()))
121     self.failUnlessEqual(0, len(cfg.GetInstanceList()))
122
123   def testUpdateCluster(self):
124     """Test updates on the cluster object"""
125     cfg = self._get_object()
126     # construct a fake cluster object
127     fake_cl = objects.Cluster()
128     # fail if we didn't read the config
129     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
130
131     cl = cfg.GetClusterInfo()
132     # first pass, must not fail
133     cfg.Update(cl, None)
134     # second pass, also must not fail (after the config has been written)
135     cfg.Update(cl, None)
136     # but the fake_cl update should still fail
137     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_cl, None)
138
139   def testUpdateNode(self):
140     """Test updates on one node object"""
141     cfg = self._get_object()
142     # construct a fake node
143     fake_node = objects.Node()
144     # fail if we didn't read the config
145     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
146                           None)
147
148     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
149     # first pass, must not fail
150     cfg.Update(node, None)
151     # second pass, also must not fail (after the config has been written)
152     cfg.Update(node, None)
153     # but the fake_node update should still fail
154     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_node,
155                           None)
156
157   def testUpdateInstance(self):
158     """Test updates on one instance object"""
159     cfg = self._get_object()
160     # construct a fake instance
161     inst = self._create_instance()
162     fake_instance = objects.Instance()
163     # fail if we didn't read the config
164     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
165                           None)
166
167     cfg.AddInstance(inst, "my-job")
168     instance = cfg.GetInstanceInfo(cfg.GetInstanceList()[0])
169     # first pass, must not fail
170     cfg.Update(instance, None)
171     # second pass, also must not fail (after the config has been written)
172     cfg.Update(instance, None)
173     # but the fake_instance update should still fail
174     self.failUnlessRaises(errors.ConfigurationError, cfg.Update, fake_instance,
175                           None)
176
177   def testNICParameterSyntaxCheck(self):
178     """Test the NIC's CheckParameterSyntax function"""
179     mode = constants.NIC_MODE
180     link = constants.NIC_LINK
181     m_bridged = constants.NIC_MODE_BRIDGED
182     m_routed = constants.NIC_MODE_ROUTED
183     CheckSyntax = objects.NIC.CheckParameterSyntax
184
185     CheckSyntax(constants.NICC_DEFAULTS)
186     CheckSyntax({mode: m_bridged, link: 'br1'})
187     CheckSyntax({mode: m_routed, link: 'default'})
188     self.assertRaises(errors.ConfigurationError,
189                       CheckSyntax, {mode: '000invalid', link: 'any'})
190     self.assertRaises(errors.ConfigurationError,
191                       CheckSyntax, {mode: m_bridged, link: None})
192     self.assertRaises(errors.ConfigurationError,
193                       CheckSyntax, {mode: m_bridged, link: ''})
194
195   def testGetNdParamsDefault(self):
196     cfg = self._get_object()
197     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
198     self.assertEqual(cfg.GetNdParams(node), constants.NDC_DEFAULTS)
199
200   def testGetNdParamsModifiedNode(self):
201     my_ndparams = {
202         constants.ND_OOB_PROGRAM: "/bin/node-oob",
203         constants.ND_SPINDLE_COUNT: 1,
204         }
205
206     cfg = self._get_object()
207     node = cfg.GetNodeInfo(cfg.GetNodeList()[0])
208     node.ndparams = my_ndparams
209     cfg.Update(node, None)
210     self.assertEqual(cfg.GetNdParams(node), my_ndparams)
211
212   def testAddGroupFillsFieldsIfMissing(self):
213     cfg = self._get_object()
214     group = objects.NodeGroup(name="test", members=[])
215     cfg.AddNodeGroup(group, "my-job")
216     self.assert_(utils.UUID_RE.match(group.uuid))
217     self.assertEqual(constants.ALLOC_POLICY_PREFERRED, group.alloc_policy)
218
219   def testAddGroupPreservesFields(self):
220     cfg = self._get_object()
221     group = objects.NodeGroup(name="test", members=[],
222                               alloc_policy=constants.ALLOC_POLICY_LAST_RESORT)
223     cfg.AddNodeGroup(group, "my-job")
224     self.assertEqual(constants.ALLOC_POLICY_LAST_RESORT, group.alloc_policy)
225
226   def testAddGroupDoesNotPreserveFields(self):
227     cfg = self._get_object()
228     group = objects.NodeGroup(name="test", members=[],
229                               serial_no=17, ctime=123, mtime=456)
230     cfg.AddNodeGroup(group, "my-job")
231     self.assertEqual(1, group.serial_no)
232     self.assert_(group.ctime > 1200000000)
233     self.assert_(group.mtime > 1200000000)
234
235   def testAddGroupCanSkipUUIDCheck(self):
236     cfg = self._get_object()
237     uuid = cfg.GenerateUniqueID("my-job")
238     group = objects.NodeGroup(name="test", members=[], uuid=uuid,
239                               serial_no=17, ctime=123, mtime=456)
240
241     self.assertRaises(errors.ConfigurationError,
242                       cfg.AddNodeGroup, group, "my-job")
243
244     cfg.AddNodeGroup(group, "my-job", check_uuid=False) # Does not raise.
245     self.assertEqual(uuid, group.uuid)
246
247   def testAssignGroupNodes(self):
248     me = netutils.Hostname()
249     cfg = self._get_object()
250
251     # Create two groups
252     grp1 = objects.NodeGroup(name="grp1", members=[],
253                              uuid="2f2fadf7-2a70-4a23-9ab5-2568c252032c")
254     grp1_serial = 1
255     cfg.AddNodeGroup(grp1, "job")
256
257     grp2 = objects.NodeGroup(name="grp2", members=[],
258                              uuid="798d0de3-680f-4a0e-b29a-0f54f693b3f1")
259     grp2_serial = 1
260     cfg.AddNodeGroup(grp2, "job")
261     self.assertEqual(set(map(operator.attrgetter("name"),
262                              cfg.GetAllNodeGroupsInfo().values())),
263                      set(["grp1", "grp2", constants.INITIAL_NODE_GROUP_NAME]))
264
265     # No-op
266     cluster_serial = cfg.GetClusterInfo().serial_no
267     cfg.AssignGroupNodes([])
268     cluster_serial += 1
269
270     # Create two nodes
271     node1 = objects.Node(name="node1", group=grp1.uuid, ndparams={})
272     node1_serial = 1
273     node2 = objects.Node(name="node2", group=grp2.uuid, ndparams={})
274     node2_serial = 1
275     cfg.AddNode(node1, "job")
276     cfg.AddNode(node2, "job")
277     cluster_serial += 2
278     self.assertEqual(set(cfg.GetNodeList()), set(["node1", "node2", me.name]))
279
280     def _VerifySerials():
281       self.assertEqual(cfg.GetClusterInfo().serial_no, cluster_serial)
282       self.assertEqual(node1.serial_no, node1_serial)
283       self.assertEqual(node2.serial_no, node2_serial)
284       self.assertEqual(grp1.serial_no, grp1_serial)
285       self.assertEqual(grp2.serial_no, grp2_serial)
286
287     _VerifySerials()
288
289     self.assertEqual(set(grp1.members), set(["node1"]))
290     self.assertEqual(set(grp2.members), set(["node2"]))
291
292     # Check invalid nodes and groups
293     self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
294       ("unknown.node.example.com", grp2.uuid),
295       ])
296     self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
297       (node1.name, "unknown-uuid"),
298       ])
299
300     self.assertEqual(node1.group, grp1.uuid)
301     self.assertEqual(node2.group, grp2.uuid)
302     self.assertEqual(set(grp1.members), set(["node1"]))
303     self.assertEqual(set(grp2.members), set(["node2"]))
304
305     # Another no-op
306     cfg.AssignGroupNodes([])
307     cluster_serial += 1
308     _VerifySerials()
309
310     # Assign to the same group (should be a no-op)
311     self.assertEqual(node2.group, grp2.uuid)
312     cfg.AssignGroupNodes([
313       (node2.name, grp2.uuid),
314       ])
315     cluster_serial += 1
316     self.assertEqual(node2.group, grp2.uuid)
317     _VerifySerials()
318     self.assertEqual(set(grp1.members), set(["node1"]))
319     self.assertEqual(set(grp2.members), set(["node2"]))
320
321     # Assign node 2 to group 1
322     self.assertEqual(node2.group, grp2.uuid)
323     cfg.AssignGroupNodes([
324       (node2.name, grp1.uuid),
325       ])
326     cluster_serial += 1
327     node2_serial += 1
328     grp1_serial += 1
329     grp2_serial += 1
330     self.assertEqual(node2.group, grp1.uuid)
331     _VerifySerials()
332     self.assertEqual(set(grp1.members), set(["node1", "node2"]))
333     self.assertFalse(grp2.members)
334
335     # And assign both nodes to group 2
336     self.assertEqual(node1.group, grp1.uuid)
337     self.assertEqual(node2.group, grp1.uuid)
338     self.assertNotEqual(grp1.uuid, grp2.uuid)
339     cfg.AssignGroupNodes([
340       (node1.name, grp2.uuid),
341       (node2.name, grp2.uuid),
342       ])
343     cluster_serial += 1
344     node1_serial += 1
345     node2_serial += 1
346     grp1_serial += 1
347     grp2_serial += 1
348     self.assertEqual(node1.group, grp2.uuid)
349     self.assertEqual(node2.group, grp2.uuid)
350     _VerifySerials()
351     self.assertFalse(grp1.members)
352     self.assertEqual(set(grp2.members), set(["node1", "node2"]))
353
354     # Destructive tests
355     orig_group = node2.group
356     try:
357       other_uuid = "68b3d087-6ea5-491c-b81f-0a47d90228c5"
358       assert compat.all(node.group != other_uuid
359                         for node in cfg.GetAllNodesInfo().values())
360       node2.group = "68b3d087-6ea5-491c-b81f-0a47d90228c5"
361       self.assertRaises(errors.ConfigurationError, cfg.AssignGroupNodes, [
362         ("node2", grp2.uuid),
363         ])
364       _VerifySerials()
365     finally:
366       node2.group = orig_group
367
368
369 class TestTRM(unittest.TestCase):
370   EC_ID = 1
371
372   def testEmpty(self):
373     t = TemporaryReservationManager()
374     t.Reserve(self.EC_ID, "a")
375     self.assertFalse(t.Reserved(self.EC_ID))
376     self.assertTrue(t.Reserved("a"))
377     self.assertEqual(len(t.GetReserved()), 1)
378
379   def testDuplicate(self):
380     t = TemporaryReservationManager()
381     t.Reserve(self.EC_ID, "a")
382     self.assertRaises(errors.ReservationError, t.Reserve, 2, "a")
383     t.DropECReservations(self.EC_ID)
384     self.assertFalse(t.Reserved("a"))
385
386
387 class TestCheckInstanceDiskIvNames(unittest.TestCase):
388   @staticmethod
389   def _MakeDisks(names):
390     return [objects.Disk(iv_name=name) for name in names]
391
392   def testNoError(self):
393     disks = self._MakeDisks(["disk/0", "disk/1"])
394     self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
395     cmdlib._UpdateIvNames(0, disks)
396     self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
397
398   def testWrongNames(self):
399     disks = self._MakeDisks(["disk/1", "disk/3", "disk/2"])
400     self.assertEqual(config._CheckInstanceDiskIvNames(disks), [
401       (0, "disk/0", "disk/1"),
402       (1, "disk/1", "disk/3"),
403       ])
404
405     # Fix names
406     cmdlib._UpdateIvNames(0, disks)
407     self.assertEqual(config._CheckInstanceDiskIvNames(disks), [])
408
409
410 if __name__ == '__main__':
411   testutils.GanetiTestProgram()