QA checks suitability for exclusive_storage tests
[ganeti-local] / qa / ganeti-qa.py
1 #!/usr/bin/python -u
2 #
3
4 # Copyright (C) 2007, 2008, 2009, 2010, 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 """Script for doing QA on Ganeti.
23
24 """
25
26 # pylint: disable=C0103
27 # due to invalid name
28
29 import copy
30 import datetime
31 import optparse
32 import sys
33
34 import qa_cluster
35 import qa_config
36 import qa_daemon
37 import qa_env
38 import qa_error
39 import qa_group
40 import qa_instance
41 import qa_network
42 import qa_node
43 import qa_os
44 import qa_job
45 import qa_rapi
46 import qa_tags
47 import qa_utils
48
49 from ganeti import utils
50 from ganeti import rapi # pylint: disable=W0611
51 from ganeti import constants
52
53 import ganeti.rapi.client # pylint: disable=W0611
54 from ganeti.rapi.client import UsesRapiClient
55
56
57 def _FormatHeader(line, end=72):
58   """Fill a line up to the end column.
59
60   """
61   line = "---- " + line + " "
62   line += "-" * (end - len(line))
63   line = line.rstrip()
64   return line
65
66
67 def _DescriptionOf(fn):
68   """Computes the description of an item.
69
70   """
71   if fn.__doc__:
72     desc = fn.__doc__.splitlines()[0].strip()
73   else:
74     desc = "%r" % fn
75
76   return desc.rstrip(".")
77
78
79 def RunTest(fn, *args, **kwargs):
80   """Runs a test after printing a header.
81
82   """
83
84   tstart = datetime.datetime.now()
85
86   desc = _DescriptionOf(fn)
87
88   print
89   print _FormatHeader("%s start %s" % (tstart, desc))
90
91   try:
92     retval = fn(*args, **kwargs)
93     return retval
94   finally:
95     tstop = datetime.datetime.now()
96     tdelta = tstop - tstart
97     print _FormatHeader("%s time=%s %s" % (tstop, tdelta, desc))
98
99
100 def RunTestIf(testnames, fn, *args, **kwargs):
101   """Runs a test conditionally.
102
103   @param testnames: either a single test name in the configuration
104       file, or a list of testnames (which will be AND-ed together)
105
106   """
107   if qa_config.TestEnabled(testnames):
108     RunTest(fn, *args, **kwargs)
109   else:
110     tstart = datetime.datetime.now()
111     desc = _DescriptionOf(fn)
112     # TODO: Formatting test names when non-string names are involved
113     print _FormatHeader("%s skipping %s, test(s) %s disabled" %
114                         (tstart, desc, testnames))
115
116
117 def RunEnvTests():
118   """Run several environment tests.
119
120   """
121   RunTestIf("env", qa_env.TestSshConnection)
122   RunTestIf("env", qa_env.TestIcmpPing)
123   RunTestIf("env", qa_env.TestGanetiCommands)
124
125
126 def SetupCluster(rapi_user, rapi_secret):
127   """Initializes the cluster.
128
129   @param rapi_user: Login user for RAPI
130   @param rapi_secret: Login secret for RAPI
131
132   """
133   RunTestIf("create-cluster", qa_cluster.TestClusterInit,
134             rapi_user, rapi_secret)
135   if not qa_config.TestEnabled("create-cluster"):
136     # If the cluster is already in place, we assume that exclusive-storage is
137     # already set according to the configuration
138     qa_config.SetExclusiveStorage(qa_config.get("exclusive-storage", False))
139
140   # Test on empty cluster
141   RunTestIf("node-list", qa_node.TestNodeList)
142   RunTestIf("instance-list", qa_instance.TestInstanceList)
143   RunTestIf("job-list", qa_job.TestJobList)
144
145   RunTestIf("create-cluster", qa_node.TestNodeAddAll)
146   if not qa_config.TestEnabled("create-cluster"):
147     # consider the nodes are already there
148     qa_node.MarkNodeAddedAll()
149
150   RunTestIf("test-jobqueue", qa_cluster.TestJobqueue)
151
152   # enable the watcher (unconditionally)
153   RunTest(qa_daemon.TestResumeWatcher)
154
155   RunTestIf("node-list", qa_node.TestNodeList)
156
157   # Test listing fields
158   RunTestIf("node-list", qa_node.TestNodeListFields)
159   RunTestIf("instance-list", qa_instance.TestInstanceListFields)
160   RunTestIf("job-list", qa_job.TestJobListFields)
161   RunTestIf("instance-export", qa_instance.TestBackupListFields)
162
163   RunTestIf("node-info", qa_node.TestNodeInfo)
164
165
166 def RunClusterTests():
167   """Runs tests related to gnt-cluster.
168
169   """
170   for test, fn in [
171     ("create-cluster", qa_cluster.TestClusterInitDisk),
172     ("cluster-renew-crypto", qa_cluster.TestClusterRenewCrypto),
173     ("cluster-verify", qa_cluster.TestClusterVerify),
174     ("cluster-reserved-lvs", qa_cluster.TestClusterReservedLvs),
175     # TODO: add more cluster modify tests
176     ("cluster-modify", qa_cluster.TestClusterModifyEmpty),
177     ("cluster-modify", qa_cluster.TestClusterModifyIPolicy),
178     ("cluster-modify", qa_cluster.TestClusterModifyISpecs),
179     ("cluster-modify", qa_cluster.TestClusterModifyBe),
180     ("cluster-modify", qa_cluster.TestClusterModifyDisk),
181     ("cluster-modify", qa_cluster.TestClusterModifyDiskTemplates),
182     ("cluster-rename", qa_cluster.TestClusterRename),
183     ("cluster-info", qa_cluster.TestClusterVersion),
184     ("cluster-info", qa_cluster.TestClusterInfo),
185     ("cluster-info", qa_cluster.TestClusterGetmaster),
186     ("cluster-redist-conf", qa_cluster.TestClusterRedistConf),
187     (["cluster-copyfile", qa_config.NoVirtualCluster],
188      qa_cluster.TestClusterCopyfile),
189     ("cluster-command", qa_cluster.TestClusterCommand),
190     ("cluster-burnin", qa_cluster.TestClusterBurnin),
191     ("cluster-master-failover", qa_cluster.TestClusterMasterFailover),
192     ("cluster-master-failover",
193      qa_cluster.TestClusterMasterFailoverWithDrainedQueue),
194     (["cluster-oob", qa_config.NoVirtualCluster],
195      qa_cluster.TestClusterOob),
196     (qa_rapi.Enabled, qa_rapi.TestVersion),
197     (qa_rapi.Enabled, qa_rapi.TestEmptyCluster),
198     (qa_rapi.Enabled, qa_rapi.TestRapiQuery),
199     ]:
200     RunTestIf(test, fn)
201
202
203 def RunRepairDiskSizes():
204   """Run the repair disk-sizes test.
205
206   """
207   RunTestIf("cluster-repair-disk-sizes", qa_cluster.TestClusterRepairDiskSizes)
208
209
210 def RunOsTests():
211   """Runs all tests related to gnt-os.
212
213   """
214   os_enabled = ["os", qa_config.NoVirtualCluster]
215
216   if qa_config.TestEnabled(qa_rapi.Enabled):
217     rapi_getos = qa_rapi.GetOperatingSystems
218   else:
219     rapi_getos = None
220
221   for fn in [
222     qa_os.TestOsList,
223     qa_os.TestOsDiagnose,
224     ]:
225     RunTestIf(os_enabled, fn)
226
227   for fn in [
228     qa_os.TestOsValid,
229     qa_os.TestOsInvalid,
230     qa_os.TestOsPartiallyValid,
231     ]:
232     RunTestIf(os_enabled, fn, rapi_getos)
233
234   for fn in [
235     qa_os.TestOsModifyValid,
236     qa_os.TestOsModifyInvalid,
237     qa_os.TestOsStatesNonExisting,
238     ]:
239     RunTestIf(os_enabled, fn)
240
241
242 def RunCommonInstanceTests(instance):
243   """Runs a few tests that are common to all disk types.
244
245   """
246   RunTestIf("instance-shutdown", qa_instance.TestInstanceShutdown, instance)
247   RunTestIf(["instance-shutdown", "instance-console", qa_rapi.Enabled],
248             qa_rapi.TestRapiStoppedInstanceConsole, instance)
249   RunTestIf(["instance-shutdown", "instance-modify"],
250             qa_instance.TestInstanceStoppedModify, instance)
251   RunTestIf("instance-shutdown", qa_instance.TestInstanceStartup, instance)
252
253   # Test shutdown/start via RAPI
254   RunTestIf(["instance-shutdown", qa_rapi.Enabled],
255             qa_rapi.TestRapiInstanceShutdown, instance)
256   RunTestIf(["instance-shutdown", qa_rapi.Enabled],
257             qa_rapi.TestRapiInstanceStartup, instance)
258
259   RunTestIf("instance-list", qa_instance.TestInstanceList)
260
261   RunTestIf("instance-info", qa_instance.TestInstanceInfo, instance)
262
263   RunTestIf("instance-modify", qa_instance.TestInstanceModify, instance)
264   RunTestIf(["instance-modify", qa_rapi.Enabled],
265             qa_rapi.TestRapiInstanceModify, instance)
266
267   RunTestIf("instance-console", qa_instance.TestInstanceConsole, instance)
268   RunTestIf(["instance-console", qa_rapi.Enabled],
269             qa_rapi.TestRapiInstanceConsole, instance)
270
271   RunTestIf("instance-device-names", qa_instance.TestInstanceDeviceNames,
272             instance)
273   DOWN_TESTS = qa_config.Either([
274     "instance-reinstall",
275     "instance-rename",
276     "instance-grow-disk",
277     ])
278
279   # shutdown instance for any 'down' tests
280   RunTestIf(DOWN_TESTS, qa_instance.TestInstanceShutdown, instance)
281
282   # now run the 'down' state tests
283   RunTestIf("instance-reinstall", qa_instance.TestInstanceReinstall, instance)
284   RunTestIf(["instance-reinstall", qa_rapi.Enabled],
285             qa_rapi.TestRapiInstanceReinstall, instance)
286
287   if qa_config.TestEnabled("instance-rename"):
288     tgt_instance = qa_config.AcquireInstance()
289     try:
290       rename_source = instance.name
291       rename_target = tgt_instance.name
292       # perform instance rename to the same name
293       RunTest(qa_instance.TestInstanceRenameAndBack,
294               rename_source, rename_source)
295       RunTestIf(qa_rapi.Enabled, qa_rapi.TestRapiInstanceRenameAndBack,
296                 rename_source, rename_source)
297       if rename_target is not None:
298         # perform instance rename to a different name, if we have one configured
299         RunTest(qa_instance.TestInstanceRenameAndBack,
300                 rename_source, rename_target)
301         RunTestIf(qa_rapi.Enabled, qa_rapi.TestRapiInstanceRenameAndBack,
302                   rename_source, rename_target)
303     finally:
304       tgt_instance.Release()
305
306   RunTestIf(["instance-grow-disk"], qa_instance.TestInstanceGrowDisk, instance)
307
308   # and now start the instance again
309   RunTestIf(DOWN_TESTS, qa_instance.TestInstanceStartup, instance)
310
311   RunTestIf("instance-reboot", qa_instance.TestInstanceReboot, instance)
312
313   RunTestIf("tags", qa_tags.TestInstanceTags, instance)
314
315   RunTestIf("cluster-verify", qa_cluster.TestClusterVerify)
316
317   RunTestIf(qa_rapi.Enabled, qa_rapi.TestInstance, instance)
318
319   # Lists instances, too
320   RunTestIf("node-list", qa_node.TestNodeList)
321
322   # Some jobs have been run, let's test listing them
323   RunTestIf("job-list", qa_job.TestJobList)
324
325
326 def RunCommonNodeTests():
327   """Run a few common node tests.
328
329   """
330   RunTestIf("node-volumes", qa_node.TestNodeVolumes)
331   RunTestIf("node-storage", qa_node.TestNodeStorage)
332   RunTestIf(["node-oob", qa_config.NoVirtualCluster], qa_node.TestOutOfBand)
333
334
335 def RunGroupListTests():
336   """Run tests for listing node groups.
337
338   """
339   RunTestIf("group-list", qa_group.TestGroupList)
340   RunTestIf("group-list", qa_group.TestGroupListFields)
341
342
343 def RunNetworkTests():
344   """Run tests for network management.
345
346   """
347   RunTestIf("network", qa_network.TestNetworkAddRemove)
348   RunTestIf("network", qa_network.TestNetworkConnect)
349
350
351 def RunGroupRwTests():
352   """Run tests for adding/removing/renaming groups.
353
354   """
355   RunTestIf("group-rwops", qa_group.TestGroupAddRemoveRename)
356   RunTestIf("group-rwops", qa_group.TestGroupAddWithOptions)
357   RunTestIf("group-rwops", qa_group.TestGroupModify)
358   RunTestIf(["group-rwops", qa_rapi.Enabled], qa_rapi.TestRapiNodeGroups)
359   RunTestIf(["group-rwops", "tags"], qa_tags.TestGroupTags,
360             qa_group.GetDefaultGroup())
361
362
363 def RunExportImportTests(instance, inodes):
364   """Tries to export and import the instance.
365
366   @type inodes: list of nodes
367   @param inodes: current nodes of the instance
368
369   """
370   # FIXME: export explicitly bails out on file based storage. other non-lvm
371   # based storage types are untested, though. Also note that import could still
372   # work, but is deeply embedded into the "export" case.
373   if (qa_config.TestEnabled("instance-export") and
374       instance.disk_template != constants.DT_FILE):
375     RunTest(qa_instance.TestInstanceExportNoTarget, instance)
376
377     pnode = inodes[0]
378     expnode = qa_config.AcquireNode(exclude=pnode)
379     try:
380       name = RunTest(qa_instance.TestInstanceExport, instance, expnode)
381
382       RunTest(qa_instance.TestBackupList, expnode)
383
384       if qa_config.TestEnabled("instance-import"):
385         newinst = qa_config.AcquireInstance()
386         try:
387           RunTest(qa_instance.TestInstanceImport, newinst, pnode,
388                   expnode, name)
389           # Check if starting the instance works
390           RunTest(qa_instance.TestInstanceStartup, newinst)
391           RunTest(qa_instance.TestInstanceRemove, newinst)
392         finally:
393           newinst.Release()
394     finally:
395       expnode.Release()
396
397   # FIXME: inter-cluster-instance-move crashes on file based instances :/
398   # See Issue 414.
399   if (qa_config.TestEnabled([qa_rapi.Enabled, "inter-cluster-instance-move"])
400       and instance.disk_template != constants.DT_FILE):
401     newinst = qa_config.AcquireInstance()
402     try:
403       tnode = qa_config.AcquireNode(exclude=inodes)
404       try:
405         RunTest(qa_rapi.TestInterClusterInstanceMove, instance, newinst,
406                 inodes, tnode)
407       finally:
408         tnode.Release()
409     finally:
410       newinst.Release()
411
412
413 def RunDaemonTests(instance):
414   """Test the ganeti-watcher script.
415
416   """
417   RunTest(qa_daemon.TestPauseWatcher)
418
419   RunTestIf("instance-automatic-restart",
420             qa_daemon.TestInstanceAutomaticRestart, instance)
421   RunTestIf("instance-consecutive-failures",
422             qa_daemon.TestInstanceConsecutiveFailures, instance)
423
424   RunTest(qa_daemon.TestResumeWatcher)
425
426
427 def RunHardwareFailureTests(instance, inodes):
428   """Test cluster internal hardware failure recovery.
429
430   """
431   RunTestIf("instance-failover", qa_instance.TestInstanceFailover, instance)
432   RunTestIf(["instance-failover", qa_rapi.Enabled],
433             qa_rapi.TestRapiInstanceFailover, instance)
434
435   RunTestIf("instance-migrate", qa_instance.TestInstanceMigrate, instance)
436   RunTestIf(["instance-migrate", qa_rapi.Enabled],
437             qa_rapi.TestRapiInstanceMigrate, instance)
438
439   if qa_config.TestEnabled("instance-replace-disks"):
440     # We just need alternative secondary nodes, hence "- 1"
441     othernodes = qa_config.AcquireManyNodes(len(inodes) - 1, exclude=inodes)
442     try:
443       RunTestIf(qa_rapi.Enabled, qa_rapi.TestRapiInstanceReplaceDisks, instance)
444       RunTest(qa_instance.TestReplaceDisks,
445               instance, inodes, othernodes)
446     finally:
447       qa_config.ReleaseManyNodes(othernodes)
448     del othernodes
449
450   if qa_config.TestEnabled("instance-recreate-disks"):
451     try:
452       acquirednodes = qa_config.AcquireManyNodes(len(inodes), exclude=inodes)
453       othernodes = acquirednodes
454     except qa_error.OutOfNodesError:
455       if len(inodes) > 1:
456         # If the cluster is not big enough, let's reuse some of the nodes, but
457         # with different roles. In this way, we can test a DRBD instance even on
458         # a 3-node cluster.
459         acquirednodes = [qa_config.AcquireNode(exclude=inodes)]
460         othernodes = acquirednodes + inodes[:-1]
461       else:
462         raise
463     try:
464       RunTest(qa_instance.TestRecreateDisks,
465               instance, inodes, othernodes)
466     finally:
467       qa_config.ReleaseManyNodes(acquirednodes)
468
469   if len(inodes) >= 2:
470     RunTestIf("node-evacuate", qa_node.TestNodeEvacuate, inodes[0], inodes[1])
471     RunTestIf("node-failover", qa_node.TestNodeFailover, inodes[0], inodes[1])
472
473
474 def RunExclusiveStorageTests():
475   """Test exclusive storage."""
476   if not qa_config.TestEnabled("cluster-exclusive-storage"):
477     return
478
479   node = qa_config.AcquireNode()
480   try:
481     old_es = qa_cluster.TestSetExclStorCluster(False)
482     qa_node.TestExclStorSingleNode(node)
483
484     qa_cluster.TestSetExclStorCluster(True)
485     qa_cluster.TestExclStorSharedPv(node)
486
487     if qa_config.TestEnabled("instance-add-plain-disk"):
488       # Make sure that the cluster doesn't have any pre-existing problem
489       qa_cluster.AssertClusterVerify()
490
491       # Create and allocate instances
492       instance1 = qa_instance.TestInstanceAddWithPlainDisk([node])
493       try:
494         instance2 = qa_instance.TestInstanceAddWithPlainDisk([node])
495         try:
496           # cluster-verify checks that disks are allocated correctly
497           qa_cluster.AssertClusterVerify()
498
499           # Remove instances
500           qa_instance.TestInstanceRemove(instance2)
501           qa_instance.TestInstanceRemove(instance1)
502         finally:
503           instance2.Release()
504       finally:
505         instance1.Release()
506
507     if qa_config.TestEnabled("instance-add-drbd-disk"):
508       snode = qa_config.AcquireNode()
509       try:
510         qa_cluster.TestSetExclStorCluster(False)
511         instance = qa_instance.TestInstanceAddWithDrbdDisk([node, snode])
512         try:
513           qa_cluster.TestSetExclStorCluster(True)
514           exp_err = [constants.CV_EINSTANCEUNSUITABLENODE]
515           qa_cluster.AssertClusterVerify(fail=True, errors=exp_err)
516           qa_instance.TestInstanceRemove(instance)
517         finally:
518           instance.Release()
519       finally:
520         snode.Release()
521     qa_cluster.TestSetExclStorCluster(old_es)
522   finally:
523     node.Release()
524
525
526 def _BuildSpecDict(par, mn, st, mx):
527   return {
528     constants.ISPECS_MINMAX: [{
529       constants.ISPECS_MIN: {par: mn},
530       constants.ISPECS_MAX: {par: mx},
531       }],
532     constants.ISPECS_STD: {par: st},
533     }
534
535
536 def _BuildDoubleSpecDict(index, par, mn, st, mx):
537   new_spec = {
538     constants.ISPECS_MINMAX: [{}, {}],
539     }
540   if st is not None:
541     new_spec[constants.ISPECS_STD] = {par: st}
542   new_spec[constants.ISPECS_MINMAX][index] = {
543     constants.ISPECS_MIN: {par: mn},
544     constants.ISPECS_MAX: {par: mx},
545     }
546   return new_spec
547
548
549 def TestIPolicyPlainInstance():
550   """Test instance policy interaction with instances"""
551   params = ["memory-size", "cpu-count", "disk-count", "disk-size", "nic-count"]
552   if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
553     print "Template %s not supported" % constants.DT_PLAIN
554     return
555
556   # This test assumes that the group policy is empty
557   (_, old_specs) = qa_cluster.TestClusterSetISpecs()
558   # We also assume to have only one min/max bound
559   assert len(old_specs[constants.ISPECS_MINMAX]) == 1
560   node = qa_config.AcquireNode()
561   try:
562     # Log of policy changes, list of tuples:
563     # (full_change, incremental_change, policy_violated)
564     history = []
565     instance = qa_instance.TestInstanceAddWithPlainDisk([node])
566     try:
567       policyerror = [constants.CV_EINSTANCEPOLICY]
568       for par in params:
569         (iminval, imaxval) = qa_instance.GetInstanceSpec(instance.name, par)
570         # Some specs must be multiple of 4
571         new_spec = _BuildSpecDict(par, imaxval + 4, imaxval + 4, imaxval + 4)
572         history.append((None, new_spec, True))
573         if iminval > 0:
574           # Some specs must be multiple of 4
575           if iminval >= 4:
576             upper = iminval - 4
577           else:
578             upper = iminval - 1
579           new_spec = _BuildSpecDict(par, 0, upper, upper)
580           history.append((None, new_spec, True))
581         history.append((old_specs, None, False))
582
583       # Test with two instance specs
584       double_specs = copy.deepcopy(old_specs)
585       double_specs[constants.ISPECS_MINMAX] = \
586           double_specs[constants.ISPECS_MINMAX] * 2
587       (par1, par2) = params[0:2]
588       (_, imaxval1) = qa_instance.GetInstanceSpec(instance.name, par1)
589       (_, imaxval2) = qa_instance.GetInstanceSpec(instance.name, par2)
590       old_minmax = old_specs[constants.ISPECS_MINMAX][0]
591       history.extend([
592         (double_specs, None, False),
593         # The first min/max limit is being violated
594         (None,
595          _BuildDoubleSpecDict(0, par1, imaxval1 + 4, imaxval1 + 4,
596                               imaxval1 + 4),
597          False),
598         # Both min/max limits are being violated
599         (None,
600          _BuildDoubleSpecDict(1, par2, imaxval2 + 4, None, imaxval2 + 4),
601          True),
602         # The second min/max limit is being violated
603         (None,
604          _BuildDoubleSpecDict(0, par1,
605                               old_minmax[constants.ISPECS_MIN][par1],
606                               old_specs[constants.ISPECS_STD][par1],
607                               old_minmax[constants.ISPECS_MAX][par1]),
608          False),
609         (old_specs, None, False),
610         ])
611
612       # Apply the changes, and check policy violations after each change
613       qa_cluster.AssertClusterVerify()
614       for (new_specs, diff_specs, failed) in history:
615         qa_cluster.TestClusterSetISpecs(new_specs=new_specs,
616                                         diff_specs=diff_specs)
617         if failed:
618           qa_cluster.AssertClusterVerify(warnings=policyerror)
619         else:
620           qa_cluster.AssertClusterVerify()
621
622       qa_instance.TestInstanceRemove(instance)
623     finally:
624       instance.Release()
625
626     # Now we replay the same policy changes, and we expect that the instance
627     # cannot be created for the cases where we had a policy violation above
628     for (new_specs, diff_specs, failed) in history:
629       qa_cluster.TestClusterSetISpecs(new_specs=new_specs,
630                                       diff_specs=diff_specs)
631       if failed:
632         qa_instance.TestInstanceAddWithPlainDisk([node], fail=True)
633       # Instance creation with no policy violation has been tested already
634   finally:
635     node.Release()
636
637
638 def IsExclusiveStorageInstanceTestEnabled():
639   test_name = "exclusive-storage-instance-tests"
640   if qa_config.TestEnabled(test_name):
641     vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
642     vgscmd = utils.ShellQuoteArgs([
643       "vgs", "--noheadings", "-o", "pv_count", vgname,
644       ])
645     nodes = qa_config.GetConfig()["nodes"]
646     for node in nodes:
647       try:
648         pvnum = int(qa_utils.GetCommandOutput(node.primary, vgscmd))
649       except Exception, e:
650         msg = ("Cannot get the number of PVs on %s, needed by '%s': %s" %
651                (node.primary, test_name, e))
652         raise qa_error.Error(msg)
653       if pvnum < 2:
654         raise qa_error.Error("Node %s has not enough PVs (%s) to run '%s'" %
655                              (node.primary, pvnum, test_name))
656     res = True
657   else:
658     res = False
659   return res
660
661
662 def RunInstanceTests():
663   """Create and exercise instances."""
664   instance_tests = [
665     ("instance-add-plain-disk", constants.DT_PLAIN,
666      qa_instance.TestInstanceAddWithPlainDisk, 1),
667     ("instance-add-drbd-disk", constants.DT_DRBD8,
668      qa_instance.TestInstanceAddWithDrbdDisk, 2),
669     ("instance-add-diskless", constants.DT_DISKLESS,
670      qa_instance.TestInstanceAddDiskless, 1),
671     ("instance-add-file", constants.DT_FILE,
672      qa_instance.TestInstanceAddFile, 1)
673     ]
674
675   for (test_name, templ, create_fun, num_nodes) in instance_tests:
676     if (qa_config.TestEnabled(test_name) and
677         qa_config.IsTemplateSupported(templ)):
678       inodes = qa_config.AcquireManyNodes(num_nodes)
679       try:
680         instance = RunTest(create_fun, inodes)
681         try:
682           RunTestIf("cluster-epo", qa_cluster.TestClusterEpo)
683           RunDaemonTests(instance)
684           for node in inodes:
685             RunTestIf("haskell-confd", qa_node.TestNodeListDrbd, node)
686           if len(inodes) > 1:
687             RunTestIf("group-rwops", qa_group.TestAssignNodesIncludingSplit,
688                       constants.INITIAL_NODE_GROUP_NAME,
689                       inodes[0].primary, inodes[1].primary)
690           if qa_config.TestEnabled("instance-convert-disk"):
691             RunTest(qa_instance.TestInstanceShutdown, instance)
692             RunTest(qa_instance.TestInstanceConvertDiskToPlain,
693                     instance, inodes)
694             RunTest(qa_instance.TestInstanceStartup, instance)
695           RunCommonInstanceTests(instance)
696           if qa_config.TestEnabled("instance-modify-primary"):
697             othernode = qa_config.AcquireNode()
698             RunTest(qa_instance.TestInstanceModifyPrimaryAndBack,
699                     instance, inodes[0], othernode)
700             othernode.Release()
701           RunGroupListTests()
702           RunExportImportTests(instance, inodes)
703           RunHardwareFailureTests(instance, inodes)
704           RunRepairDiskSizes()
705           RunTest(qa_instance.TestInstanceRemove, instance)
706         finally:
707           instance.Release()
708         del instance
709       finally:
710         qa_config.ReleaseManyNodes(inodes)
711       qa_cluster.AssertClusterVerify()
712
713
714 def RunQa():
715   """Main QA body.
716
717   """
718   rapi_user = "ganeti-qa"
719   rapi_secret = utils.GenerateSecret()
720
721   RunEnvTests()
722   SetupCluster(rapi_user, rapi_secret)
723
724   if qa_rapi.Enabled():
725     # Load RAPI certificate
726     qa_rapi.Setup(rapi_user, rapi_secret)
727
728   RunClusterTests()
729   RunOsTests()
730
731   RunTestIf("tags", qa_tags.TestClusterTags)
732
733   RunCommonNodeTests()
734   RunGroupListTests()
735   RunGroupRwTests()
736   RunNetworkTests()
737
738   # The master shouldn't be readded or put offline; "delay" needs a non-master
739   # node to test
740   pnode = qa_config.AcquireNode(exclude=qa_config.GetMasterNode())
741   try:
742     RunTestIf("node-readd", qa_node.TestNodeReadd, pnode)
743     RunTestIf("node-modify", qa_node.TestNodeModify, pnode)
744     RunTestIf("delay", qa_cluster.TestDelay, pnode)
745   finally:
746     pnode.Release()
747
748   # Make sure the cluster is clean before running instance tests
749   qa_cluster.AssertClusterVerify()
750
751   pnode = qa_config.AcquireNode()
752   try:
753     RunTestIf("tags", qa_tags.TestNodeTags, pnode)
754
755     if qa_rapi.Enabled():
756       RunTest(qa_rapi.TestNode, pnode)
757
758       if qa_config.TestEnabled("instance-add-plain-disk"):
759         for use_client in [True, False]:
760           rapi_instance = RunTest(qa_rapi.TestRapiInstanceAdd, pnode,
761                                   use_client)
762           try:
763             if qa_config.TestEnabled("instance-plain-rapi-common-tests"):
764               RunCommonInstanceTests(rapi_instance)
765             RunTest(qa_rapi.TestRapiInstanceRemove, rapi_instance, use_client)
766           finally:
767             rapi_instance.Release()
768           del rapi_instance
769
770   finally:
771     pnode.Release()
772
773   config_list = [
774     ("default-instance-tests", lambda: None, lambda _: None),
775     (IsExclusiveStorageInstanceTestEnabled,
776      lambda: qa_cluster.TestSetExclStorCluster(True),
777      qa_cluster.TestSetExclStorCluster),
778   ]
779   for (conf_name, setup_conf_f, restore_conf_f) in config_list:
780     if qa_config.TestEnabled(conf_name):
781       oldconf = setup_conf_f()
782       RunInstanceTests()
783       restore_conf_f(oldconf)
784
785   pnode = qa_config.AcquireNode()
786   try:
787     if qa_config.TestEnabled(["instance-add-plain-disk", "instance-export"]):
788       for shutdown in [False, True]:
789         instance = RunTest(qa_instance.TestInstanceAddWithPlainDisk, [pnode])
790         try:
791           expnode = qa_config.AcquireNode(exclude=pnode)
792           try:
793             if shutdown:
794               # Stop instance before exporting and removing it
795               RunTest(qa_instance.TestInstanceShutdown, instance)
796             RunTest(qa_instance.TestInstanceExportWithRemove, instance, expnode)
797             RunTest(qa_instance.TestBackupList, expnode)
798           finally:
799             expnode.Release()
800         finally:
801           instance.Release()
802         del expnode
803         del instance
804       qa_cluster.AssertClusterVerify()
805
806   finally:
807     pnode.Release()
808
809   RunExclusiveStorageTests()
810   RunTestIf(["cluster-instance-policy", "instance-add-plain-disk"],
811             TestIPolicyPlainInstance)
812
813   RunTestIf(
814     "instance-add-restricted-by-disktemplates",
815     qa_instance.TestInstanceCreationRestrictedByDiskTemplates)
816
817   # Test removing instance with offline drbd secondary
818   if qa_config.TestEnabled(["instance-remove-drbd-offline",
819                             "instance-add-drbd-disk"]):
820     # Make sure the master is not put offline
821     snode = qa_config.AcquireNode(exclude=qa_config.GetMasterNode())
822     try:
823       pnode = qa_config.AcquireNode(exclude=snode)
824       try:
825         instance = qa_instance.TestInstanceAddWithDrbdDisk([pnode, snode])
826         set_offline = lambda node: qa_node.MakeNodeOffline(node, "yes")
827         set_online = lambda node: qa_node.MakeNodeOffline(node, "no")
828         RunTest(qa_instance.TestRemoveInstanceOfflineNode, instance, snode,
829                 set_offline, set_online)
830       finally:
831         pnode.Release()
832     finally:
833       snode.Release()
834     qa_cluster.AssertClusterVerify()
835
836   RunTestIf("create-cluster", qa_node.TestNodeRemoveAll)
837
838   RunTestIf("cluster-destroy", qa_cluster.TestClusterDestroy)
839
840
841 @UsesRapiClient
842 def main():
843   """Main program.
844
845   """
846   parser = optparse.OptionParser(usage="%prog [options] <config-file>")
847   parser.add_option("--yes-do-it", dest="yes_do_it",
848                     action="store_true",
849                     help="Really execute the tests")
850   (opts, args) = parser.parse_args()
851
852   if len(args) == 1:
853     (config_file, ) = args
854   else:
855     parser.error("Wrong number of arguments.")
856
857   if not opts.yes_do_it:
858     print ("Executing this script irreversibly destroys any Ganeti\n"
859            "configuration on all nodes involved. If you really want\n"
860            "to start testing, supply the --yes-do-it option.")
861     sys.exit(1)
862
863   qa_config.Load(config_file)
864
865   primary = qa_config.GetMasterNode().primary
866   qa_utils.StartMultiplexer(primary)
867   print ("SSH command for primary node: %s" %
868          utils.ShellQuoteArgs(qa_utils.GetSSHCommand(primary, "")))
869   print ("SSH command for other nodes: %s" %
870          utils.ShellQuoteArgs(qa_utils.GetSSHCommand("NODE", "")))
871   try:
872     RunQa()
873   finally:
874     qa_utils.CloseMultiplexers()
875
876 if __name__ == "__main__":
877   main()