Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / cluster_unittest.py @ 08cef8fc

History | View | Annotate | Download (15.1 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2008, 2011, 2012, 2013 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
"""Tests for LUCluster*
23

24
"""
25

    
26
import unittest
27
import operator
28
import os
29
import tempfile
30
import shutil
31

    
32
from ganeti import constants
33
from ganeti import compat
34
from ganeti import ht
35
from ganeti import netutils
36
from ganeti import objects
37
from ganeti import opcodes
38
from ganeti import utils
39
from ganeti import pathutils
40
from ganeti import rpc
41
from ganeti import query
42
from ganeti.cmdlib import cluster
43
from ganeti.hypervisor import hv_xen
44

    
45
from testsupport import *
46

    
47
import testutils
48
import mocks
49

    
50

    
51
class TestCertVerification(testutils.GanetiTestCase):
52
  def setUp(self):
53
    testutils.GanetiTestCase.setUp(self)
54

    
55
    self.tmpdir = tempfile.mkdtemp()
56

    
57
  def tearDown(self):
58
    shutil.rmtree(self.tmpdir)
59

    
60
  def testVerifyCertificate(self):
61
    cluster._VerifyCertificate(testutils.TestDataFilename("cert1.pem"))
62

    
63
    nonexist_filename = os.path.join(self.tmpdir, "does-not-exist")
64

    
65
    (errcode, msg) = cluster._VerifyCertificate(nonexist_filename)
66
    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
67

    
68
    # Try to load non-certificate file
69
    invalid_cert = testutils.TestDataFilename("bdev-net.txt")
70
    (errcode, msg) = cluster._VerifyCertificate(invalid_cert)
71
    self.assertEqual(errcode, cluster.LUClusterVerifyConfig.ETYPE_ERROR)
72

    
73

    
74
class TestClusterVerifySsh(unittest.TestCase):
75
  def testMultipleGroups(self):
76
    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
77
    mygroupnodes = [
78
      objects.Node(name="node20", group="my", offline=False),
79
      objects.Node(name="node21", group="my", offline=False),
80
      objects.Node(name="node22", group="my", offline=False),
81
      objects.Node(name="node23", group="my", offline=False),
82
      objects.Node(name="node24", group="my", offline=False),
83
      objects.Node(name="node25", group="my", offline=False),
84
      objects.Node(name="node26", group="my", offline=True),
85
      ]
86
    nodes = [
87
      objects.Node(name="node1", group="g1", offline=True),
88
      objects.Node(name="node2", group="g1", offline=False),
89
      objects.Node(name="node3", group="g1", offline=False),
90
      objects.Node(name="node4", group="g1", offline=True),
91
      objects.Node(name="node5", group="g1", offline=False),
92
      objects.Node(name="node10", group="xyz", offline=False),
93
      objects.Node(name="node11", group="xyz", offline=False),
94
      objects.Node(name="node40", group="alloff", offline=True),
95
      objects.Node(name="node41", group="alloff", offline=True),
96
      objects.Node(name="node50", group="aaa", offline=False),
97
      ] + mygroupnodes
98
    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
99

    
100
    (online, perhost) = fn(mygroupnodes, "my", nodes)
101
    self.assertEqual(online, ["node%s" % i for i in range(20, 26)])
102
    self.assertEqual(set(perhost.keys()), set(online))
103

    
104
    self.assertEqual(perhost, {
105
      "node20": ["node10", "node2", "node50"],
106
      "node21": ["node11", "node3", "node50"],
107
      "node22": ["node10", "node5", "node50"],
108
      "node23": ["node11", "node2", "node50"],
109
      "node24": ["node10", "node3", "node50"],
110
      "node25": ["node11", "node5", "node50"],
111
      })
112

    
113
  def testSingleGroup(self):
114
    fn = cluster.LUClusterVerifyGroup._SelectSshCheckNodes
115
    nodes = [
116
      objects.Node(name="node1", group="default", offline=True),
117
      objects.Node(name="node2", group="default", offline=False),
118
      objects.Node(name="node3", group="default", offline=False),
119
      objects.Node(name="node4", group="default", offline=True),
120
      ]
121
    assert not utils.FindDuplicates(map(operator.attrgetter("name"), nodes))
122

    
123
    (online, perhost) = fn(nodes, "default", nodes)
124
    self.assertEqual(online, ["node2", "node3"])
125
    self.assertEqual(set(perhost.keys()), set(online))
126

    
127
    self.assertEqual(perhost, {
128
      "node2": [],
129
      "node3": [],
130
      })
131

    
132

    
133
class TestClusterVerifyFiles(unittest.TestCase):
134
  @staticmethod
135
  def _FakeErrorIf(errors, cond, ecode, item, msg, *args, **kwargs):
136
    assert ((ecode == constants.CV_ENODEFILECHECK and
137
             ht.TNonEmptyString(item)) or
138
            (ecode == constants.CV_ECLUSTERFILECHECK and
139
             item is None))
140

    
141
    if args:
142
      msg = msg % args
143

    
144
    if cond:
145
      errors.append((item, msg))
146

    
147
  def test(self):
148
    errors = []
149
    nodeinfo = [
150
      objects.Node(name="master.example.com",
151
                   uuid="master-uuid",
152
                   offline=False,
153
                   vm_capable=True),
154
      objects.Node(name="node2.example.com",
155
                   uuid="node2-uuid",
156
                   offline=False,
157
                   vm_capable=True),
158
      objects.Node(name="node3.example.com",
159
                   uuid="node3-uuid",
160
                   master_candidate=True,
161
                   vm_capable=False),
162
      objects.Node(name="node4.example.com",
163
                   uuid="node4-uuid",
164
                   offline=False,
165
                   vm_capable=True),
166
      objects.Node(name="nodata.example.com",
167
                   uuid="nodata-uuid",
168
                   offline=False,
169
                   vm_capable=True),
170
      objects.Node(name="offline.example.com",
171
                   uuid="offline-uuid",
172
                   offline=True),
173
      ]
174
    files_all = set([
175
      pathutils.CLUSTER_DOMAIN_SECRET_FILE,
176
      pathutils.RAPI_CERT_FILE,
177
      pathutils.RAPI_USERS_FILE,
178
      ])
179
    files_opt = set([
180
      pathutils.RAPI_USERS_FILE,
181
      hv_xen.XL_CONFIG_FILE,
182
      pathutils.VNC_PASSWORD_FILE,
183
      ])
184
    files_mc = set([
185
      pathutils.CLUSTER_CONF_FILE,
186
      ])
187
    files_vm = set([
188
      hv_xen.XEND_CONFIG_FILE,
189
      hv_xen.XL_CONFIG_FILE,
190
      pathutils.VNC_PASSWORD_FILE,
191
      ])
192
    nvinfo = {
193
      "master-uuid": rpc.RpcResult(data=(True, {
194
        constants.NV_FILELIST: {
195
          pathutils.CLUSTER_CONF_FILE: "82314f897f38b35f9dab2f7c6b1593e0",
196
          pathutils.RAPI_CERT_FILE: "babbce8f387bc082228e544a2146fee4",
197
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
198
          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
199
          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
200
        }})),
201
      "node2-uuid": rpc.RpcResult(data=(True, {
202
        constants.NV_FILELIST: {
203
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
204
          hv_xen.XEND_CONFIG_FILE: "b4a8a824ab3cac3d88839a9adeadf310",
205
          }
206
        })),
207
      "node3-uuid": rpc.RpcResult(data=(True, {
208
        constants.NV_FILELIST: {
209
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
210
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
211
          }
212
        })),
213
      "node4-uuid": rpc.RpcResult(data=(True, {
214
        constants.NV_FILELIST: {
215
          pathutils.RAPI_CERT_FILE: "97f0356500e866387f4b84233848cc4a",
216
          pathutils.CLUSTER_CONF_FILE: "conf-a6d4b13e407867f7a7b4f0f232a8f527",
217
          pathutils.CLUSTER_DOMAIN_SECRET_FILE: "cds-47b5b3f19202936bb4",
218
          pathutils.RAPI_USERS_FILE: "rapiusers-ea3271e8d810ef3",
219
          hv_xen.XL_CONFIG_FILE: "77935cee92afd26d162f9e525e3d49b9"
220
          }
221
        })),
222
      "nodata-uuid": rpc.RpcResult(data=(True, {})),
223
      "offline-uuid": rpc.RpcResult(offline=True),
224
      }
225
    assert set(nvinfo.keys()) == set(map(operator.attrgetter("uuid"), nodeinfo))
226

    
227
    verify_lu = cluster.LUClusterVerifyGroup(mocks.FakeProc(),
228
                                             opcodes.OpClusterVerify(),
229
                                             mocks.FakeContext(),
230
                                             None)
231

    
232
    verify_lu._ErrorIf = compat.partial(self._FakeErrorIf, errors)
233

    
234
    # TODO: That's a bit hackish to mock only this single method. We should
235
    # build a better FakeConfig which provides such a feature already.
236
    def GetNodeName(node_uuid):
237
      for node in nodeinfo:
238
        if node.uuid == node_uuid:
239
          return node.name
240
      return None
241

    
242
    verify_lu.cfg.GetNodeName = GetNodeName
243

    
244
    verify_lu._VerifyFiles(nodeinfo, "master-uuid", nvinfo,
245
                           (files_all, files_opt, files_mc, files_vm))
246
    self.assertEqual(sorted(errors), sorted([
247
      (None, ("File %s found with 2 different checksums (variant 1 on"
248
              " node2.example.com, node3.example.com, node4.example.com;"
249
              " variant 2 on master.example.com)" % pathutils.RAPI_CERT_FILE)),
250
      (None, ("File %s is missing from node(s) node2.example.com" %
251
              pathutils.CLUSTER_DOMAIN_SECRET_FILE)),
252
      (None, ("File %s should not exist on node(s) node4.example.com" %
253
              pathutils.CLUSTER_CONF_FILE)),
254
      (None, ("File %s is missing from node(s) node4.example.com" %
255
              hv_xen.XEND_CONFIG_FILE)),
256
      (None, ("File %s is missing from node(s) node3.example.com" %
257
              pathutils.CLUSTER_CONF_FILE)),
258
      (None, ("File %s found with 2 different checksums (variant 1 on"
259
              " master.example.com; variant 2 on node4.example.com)" %
260
              pathutils.CLUSTER_CONF_FILE)),
261
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
262
              " found on master.example.com, node2.example.com,"
263
              " node3.example.com)" % pathutils.RAPI_USERS_FILE)),
264
      (None, ("File %s is optional, but it must exist on all or no nodes (not"
265
              " found on node2.example.com)" % hv_xen.XL_CONFIG_FILE)),
266
      ("nodata.example.com", "Node did not return file checksum data"),
267
      ]))
268

    
269

    
270
class TestLUClusterActivateMasterIp(CmdlibTestCase):
271
  def testSuccess(self):
272
    op = opcodes.OpClusterActivateMasterIp()
273

    
274
    self.rpc.call_node_activate_master_ip.return_value = \
275
      RpcResultsBuilder(cfg=self.cfg) \
276
        .CreateSuccessfulNodeResult(self.cfg.GetMasterNode())
277

    
278
    self.ExecOpCode(op)
279

    
280
    self.rpc.call_node_activate_master_ip.assert_called_once_with(
281
      self.cfg.GetMasterNode(),
282
      self.cfg.GetMasterNetworkParameters(),
283
      False)
284

    
285
  def testFailure(self):
286
    op = opcodes.OpClusterActivateMasterIp()
287

    
288
    self.rpc.call_node_activate_master_ip.return_value = \
289
      RpcResultsBuilder(cfg=self.cfg) \
290
        .CreateFailedNodeResult(self.cfg.GetMasterNode()) \
291

    
292
    self.ExecOpCodeExpectOpExecError(op)
293

    
294

    
295
class TestLUClusterDeactivateMasterIp(CmdlibTestCase):
296
  def testSuccess(self):
297
    op = opcodes.OpClusterDeactivateMasterIp()
298

    
299
    self.rpc.call_node_deactivate_master_ip.return_value = \
300
      RpcResultsBuilder(cfg=self.cfg) \
301
        .CreateSuccessfulNodeResult(self.cfg.GetMasterNode())
302

    
303
    self.ExecOpCode(op)
304

    
305
    self.rpc.call_node_deactivate_master_ip.assert_called_once_with(
306
      self.cfg.GetMasterNode(),
307
      self.cfg.GetMasterNetworkParameters(),
308
      False)
309

    
310
  def testFailure(self):
311
    op = opcodes.OpClusterDeactivateMasterIp()
312

    
313
    self.rpc.call_node_deactivate_master_ip.return_value = \
314
      RpcResultsBuilder(cfg=self.cfg) \
315
        .CreateFailedNodeResult(self.cfg.GetMasterNode()) \
316

    
317
    self.ExecOpCodeExpectOpExecError(op)
318

    
319

    
320
class TestLUClusterConfigQuery(CmdlibTestCase):
321
  def testInvalidField(self):
322
    op = opcodes.OpClusterConfigQuery(output_fields=["pinky_bunny"])
323

    
324
    self.ExecOpCodeExpectOpPrereqError(op, "pinky_bunny")
325

    
326
  def testAllFields(self):
327
    op = opcodes.OpClusterConfigQuery(output_fields=query.CLUSTER_FIELDS.keys())
328

    
329
    self.rpc.call_get_watcher_pause.return_value = \
330
      RpcResultsBuilder(self.cfg) \
331
        .CreateSuccessfulNodeResult(self.cfg.GetMasterNode(), -1)
332

    
333
    ret = self.ExecOpCode(op)
334

    
335
    self.assertEqual(1, self.rpc.call_get_watcher_pause.call_count)
336
    self.assertEqual(len(ret), len(query.CLUSTER_FIELDS))
337

    
338
  def testEmpytFields(self):
339
    op = opcodes.OpClusterConfigQuery(output_fields=[])
340

    
341
    self.ExecOpCode(op)
342

    
343
    self.assertFalse(self.rpc.call_get_watcher_pause.called)
344

    
345

    
346
class TestLUClusterDestroy(CmdlibTestCase):
347
  def testExistingNodes(self):
348
    op = opcodes.OpClusterDestroy()
349

    
350
    self.cfg.AddNewNode()
351
    self.cfg.AddNewNode()
352

    
353
    self.ExecOpCodeExpectOpPrereqError(op, "still 2 node\(s\)")
354

    
355
  def testExistingInstances(self):
356
    op = opcodes.OpClusterDestroy()
357

    
358
    self.cfg.AddNewInstance()
359
    self.cfg.AddNewInstance()
360

    
361
    self.ExecOpCodeExpectOpPrereqError(op, "still 2 instance\(s\)")
362

    
363
  def testEmptyCluster(self):
364
    op = opcodes.OpClusterDestroy()
365

    
366
    self.ExecOpCode(op)
367

    
368
    self.assertSingleHooksCall([self.cfg.GetMasterNodeName()],
369
                               "cluster-destroy",
370
                               constants.HOOKS_PHASE_POST)
371

    
372

    
373
class TestLUClusterPostInit(CmdlibTestCase):
374
  def testExecuion(self):
375
    op = opcodes.OpClusterPostInit()
376

    
377
    self.ExecOpCode(op)
378

    
379
    self.assertSingleHooksCall([self.cfg.GetMasterNodeName()],
380
                               "cluster-init",
381
                               constants.HOOKS_PHASE_POST)
382

    
383

    
384
class TestLUClusterQuery(CmdlibTestCase):
385
  def testSimpleInvocation(self):
386
    op = opcodes.OpClusterQuery()
387

    
388
    self.ExecOpCode(op)
389

    
390
  def testIPv6Cluster(self):
391
    op = opcodes.OpClusterQuery()
392

    
393
    self.cfg.GetClusterInfo().primary_ip_family = netutils.IP6Address.family
394

    
395
    self.ExecOpCode(op)
396

    
397

    
398
class TestLUClusterRedistConf(CmdlibTestCase):
399
  def testSimpleInvocation(self):
400
    op = opcodes.OpClusterRedistConf()
401

    
402
    self.ExecOpCode(op)
403

    
404

    
405
class TestLUClusterRename(CmdlibTestCase):
406
  NEW_NAME = "new-name.example.com"
407
  NEW_IP = "1.2.3.4"
408

    
409
  def testNoChanges(self):
410
    op = opcodes.OpClusterRename(name=self.cfg.GetClusterName())
411

    
412
    self.ExecOpCodeExpectOpPrereqError(op, "name nor the IP address")
413

    
414
  def testReachableIp(self):
415
    op = opcodes.OpClusterRename(name=self.NEW_NAME)
416

    
417
    self.netutils_mod.GetHostname.return_value = \
418
      HostnameMock(self.NEW_NAME, self.NEW_IP)
419
    self.netutils_mod.TcpPing.return_value = True
420

    
421
    self.ExecOpCodeExpectOpPrereqError(op, "is reachable on the network")
422

    
423
  def testValidRename(self):
424
    op = opcodes.OpClusterRename(name=self.NEW_NAME)
425

    
426
    self.netutils_mod.GetHostname.return_value = \
427
      HostnameMock(self.NEW_NAME, self.NEW_IP)
428

    
429
    self.ExecOpCode(op)
430

    
431
    self.assertEqual(1, self.ssh_mod.WriteKnownHostsFile.call_count)
432
    self.rpc.call_node_deactivate_master_ip.assert_called_once_with(
433
      self.cfg.GetMasterNode(),
434
      self.cfg.GetMasterNetworkParameters(),
435
      False)
436
    self.rpc.call_node_activate_master_ip.assert_called_once_with(
437
      self.cfg.GetMasterNode(),
438
      self.cfg.GetMasterNetworkParameters(),
439
      False)
440

    
441
  def testRenameOfflineMaster(self):
442
    op = opcodes.OpClusterRename(name=self.NEW_NAME)
443

    
444
    self.cfg.GetMasterNodeInfo().offline = True
445
    self.netutils_mod.GetHostname.return_value = \
446
      HostnameMock(self.NEW_NAME, self.NEW_IP)
447

    
448
    self.ExecOpCode(op)
449

    
450

    
451
if __name__ == "__main__":
452
  testutils.GanetiTestProgram()