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