Bump new upstream version
[ganeti-local] / qa / qa_cluster.py
1 #
2 #
3
4 # Copyright (C) 2007, 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 """Cluster related QA tests.
23
24 """
25
26 import re
27 import tempfile
28 import os.path
29
30 from ganeti import constants
31 from ganeti import compat
32 from ganeti import utils
33 from ganeti import pathutils
34
35 import qa_config
36 import qa_utils
37 import qa_error
38 import qa_instance
39
40 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41
42
43 # Prefix for LVM volumes created by QA code during tests
44 _QA_LV_PREFIX = "qa-"
45
46 #: cluster verify command
47 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
48
49
50 def _RemoveFileFromAllNodes(filename):
51   """Removes a file from all nodes.
52
53   """
54   for node in qa_config.get("nodes"):
55     AssertCommand(["rm", "-f", filename], node=node)
56
57
58 def _CheckFileOnAllNodes(filename, content):
59   """Verifies the content of the given file on all nodes.
60
61   """
62   cmd = utils.ShellQuoteArgs(["cat", filename])
63   for node in qa_config.get("nodes"):
64     AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
65
66
67 def _GetClusterField(field_path):
68   """Get the value of a cluster field.
69
70   @type field_path: list of strings
71   @param field_path: Names of the groups/fields to navigate to get the desired
72       value, e.g. C{["Default node parameters", "oob_program"]}
73   @return: The effective value of the field (the actual type depends on the
74       chosen field)
75
76   """
77   assert isinstance(field_path, list)
78   assert field_path
79   ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
80   for key in field_path:
81     ret = ret[key]
82   return ret
83
84
85 # Cluster-verify errors (date, "ERROR", then error code)
86 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
87
88
89 def _GetCVErrorCodes(cvout):
90   errs = set()
91   warns = set()
92   for l in cvout.splitlines():
93     m = _CVERROR_RE.match(l)
94     if m:
95       etype = m.group(1)
96       ecode = m.group(2)
97       if etype == "ERROR":
98         errs.add(ecode)
99       elif etype == "WARNING":
100         warns.add(ecode)
101   return (errs, warns)
102
103
104 def _CheckVerifyErrors(actual, expected, etype):
105   exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
106   if not actual.issuperset(exp_codes):
107     missing = exp_codes.difference(actual)
108     raise qa_error.Error("Cluster-verify didn't return these expected"
109                          " %ss: %s" % (etype, utils.CommaJoin(missing)))
110
111
112 def AssertClusterVerify(fail=False, errors=None, warnings=None):
113   """Run cluster-verify and check the result
114
115   @type fail: bool
116   @param fail: if cluster-verify is expected to fail instead of succeeding
117   @type errors: list of tuples
118   @param errors: List of CV_XXX errors that are expected; if specified, all the
119       errors listed must appear in cluster-verify output. A non-empty value
120       implies C{fail=True}.
121   @type warnings: list of tuples
122   @param warnings: Same as C{errors} but for warnings.
123
124   """
125   cvcmd = "gnt-cluster verify"
126   mnode = qa_config.GetMasterNode()
127   if errors or warnings:
128     cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
129                              fail=(fail or errors))
130     (act_errs, act_warns) = _GetCVErrorCodes(cvout)
131     if errors:
132       _CheckVerifyErrors(act_errs, errors, "error")
133     if warnings:
134       _CheckVerifyErrors(act_warns, warnings, "warning")
135   else:
136     AssertCommand(cvcmd, fail=fail, node=mnode)
137
138
139 # data for testing failures due to bad keys/values for disk parameters
140 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
141                 "drbd:nonexistent=1",
142                 "drbd:resync-rate=invalid",
143                 ]
144
145
146 def TestClusterInitDisk():
147   """gnt-cluster init -D"""
148   name = qa_config.get("name")
149   for param in _FAIL_PARAMS:
150     AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
151
152
153 def TestClusterInit(rapi_user, rapi_secret):
154   """gnt-cluster init"""
155   master = qa_config.GetMasterNode()
156
157   rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
158   rapi_dir = os.path.dirname(rapi_users_path)
159
160   # First create the RAPI credentials
161   fh = tempfile.NamedTemporaryFile()
162   try:
163     fh.write("%s %s write\n" % (rapi_user, rapi_secret))
164     fh.flush()
165
166     tmpru = qa_utils.UploadFile(master.primary, fh.name)
167     try:
168       AssertCommand(["mkdir", "-p", rapi_dir])
169       AssertCommand(["mv", tmpru, rapi_users_path])
170     finally:
171       AssertCommand(["rm", "-f", tmpru])
172   finally:
173     fh.close()
174
175   # Initialize cluster
176   cmd = [
177     "gnt-cluster", "init",
178     "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
179     "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
180     "--enabled-disk-templates=%s" %
181       ",".join(qa_config.GetEnabledDiskTemplates())
182     ]
183
184   for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
185                     "nic-count"):
186     for spec_val in ("min", "max", "std"):
187       spec = qa_config.get("ispec_%s_%s" %
188                            (spec_type.replace("-", "_"), spec_val), None)
189       if spec is not None:
190         cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
191
192   if master.secondary:
193     cmd.append("--secondary-ip=%s" % master.secondary)
194
195   vgname = qa_config.get("vg-name", None)
196   if vgname:
197     cmd.append("--vg-name=%s" % vgname)
198
199   master_netdev = qa_config.get("master-netdev", None)
200   if master_netdev:
201     cmd.append("--master-netdev=%s" % master_netdev)
202
203   nicparams = qa_config.get("default-nicparams", None)
204   if nicparams:
205     cmd.append("--nic-parameters=%s" %
206                ",".join(utils.FormatKeyValue(nicparams)))
207
208   # Cluster value of the exclusive-storage node parameter
209   e_s = qa_config.get("exclusive-storage")
210   if e_s is not None:
211     cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
212   else:
213     e_s = False
214   qa_config.SetExclusiveStorage(e_s)
215
216   extra_args = qa_config.get("cluster-init-args")
217   if extra_args:
218     cmd.extend(extra_args)
219
220   cmd.append(qa_config.get("name"))
221
222   AssertCommand(cmd)
223
224   cmd = ["gnt-cluster", "modify"]
225
226   # hypervisor parameter modifications
227   hvp = qa_config.get("hypervisor-parameters", {})
228   for k, v in hvp.items():
229     cmd.extend(["-H", "%s:%s" % (k, v)])
230   # backend parameter modifications
231   bep = qa_config.get("backend-parameters", "")
232   if bep:
233     cmd.extend(["-B", bep])
234
235   if len(cmd) > 2:
236     AssertCommand(cmd)
237
238   # OS parameters
239   osp = qa_config.get("os-parameters", {})
240   for k, v in osp.items():
241     AssertCommand(["gnt-os", "modify", "-O", v, k])
242
243   # OS hypervisor parameters
244   os_hvp = qa_config.get("os-hvp", {})
245   for os_name in os_hvp:
246     for hv, hvp in os_hvp[os_name].items():
247       AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
248
249
250 def TestClusterRename():
251   """gnt-cluster rename"""
252   cmd = ["gnt-cluster", "rename", "-f"]
253
254   original_name = qa_config.get("name")
255   rename_target = qa_config.get("rename", None)
256   if rename_target is None:
257     print qa_utils.FormatError('"rename" entry is missing')
258     return
259
260   for data in [
261     cmd + [rename_target],
262     _CLUSTER_VERIFY,
263     cmd + [original_name],
264     _CLUSTER_VERIFY,
265     ]:
266     AssertCommand(data)
267
268
269 def TestClusterOob():
270   """out-of-band framework"""
271   oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
272
273   AssertCommand(_CLUSTER_VERIFY)
274   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
275                  "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
276                  utils.NewUUID()])
277
278   AssertCommand(_CLUSTER_VERIFY, fail=True)
279
280   AssertCommand(["touch", oob_path_exists])
281   AssertCommand(["chmod", "0400", oob_path_exists])
282   AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
283
284   try:
285     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
286                    "oob_program=%s" % oob_path_exists])
287
288     AssertCommand(_CLUSTER_VERIFY, fail=True)
289
290     AssertCommand(["chmod", "0500", oob_path_exists])
291     AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
292
293     AssertCommand(_CLUSTER_VERIFY)
294   finally:
295     AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
296
297   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
298                  "oob_program="])
299
300
301 def TestClusterEpo():
302   """gnt-cluster epo"""
303   master = qa_config.GetMasterNode()
304
305   # Assert that OOB is unavailable for all nodes
306   result_output = GetCommandOutput(master.primary,
307                                    "gnt-node list --verbose --no-headers -o"
308                                    " powered")
309   AssertEqual(compat.all(powered == "(unavail)"
310                          for powered in result_output.splitlines()), True)
311
312   # Conflicting
313   AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
314   # --all doesn't expect arguments
315   AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
316
317   # Unless --all is given master is not allowed to be in the list
318   AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
319
320   # This shouldn't fail
321   AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
322
323   # All instances should have been stopped now
324   result_output = GetCommandOutput(master.primary,
325                                    "gnt-instance list --no-headers -o status")
326   # ERROR_down because the instance is stopped but not recorded as such
327   AssertEqual(compat.all(status == "ERROR_down"
328                          for status in result_output.splitlines()), True)
329
330   # Now start everything again
331   AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
332
333   # All instances should have been started now
334   result_output = GetCommandOutput(master.primary,
335                                    "gnt-instance list --no-headers -o status")
336   AssertEqual(compat.all(status == "running"
337                          for status in result_output.splitlines()), True)
338
339
340 def TestClusterVerify():
341   """gnt-cluster verify"""
342   AssertCommand(_CLUSTER_VERIFY)
343   AssertCommand(["gnt-cluster", "verify-disks"])
344
345
346 def TestJobqueue():
347   """gnt-debug test-jobqueue"""
348   AssertCommand(["gnt-debug", "test-jobqueue"])
349
350
351 def TestDelay(node):
352   """gnt-debug delay"""
353   AssertCommand(["gnt-debug", "delay", "1"])
354   AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
355   AssertCommand(["gnt-debug", "delay", "--no-master",
356                  "-n", node.primary, "1"])
357
358
359 def TestClusterReservedLvs():
360   """gnt-cluster reserved lvs"""
361   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
362   lvname = _QA_LV_PREFIX + "test"
363   lvfullname = "/".join([vgname, lvname])
364   for fail, cmd in [
365     (False, _CLUSTER_VERIFY),
366     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
367     (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
368     (True, _CLUSTER_VERIFY),
369     (False, ["gnt-cluster", "modify", "--reserved-lvs",
370              "%s,.*/other-test" % lvfullname]),
371     (False, _CLUSTER_VERIFY),
372     (False, ["gnt-cluster", "modify", "--reserved-lvs",
373              ".*/%s.*" % _QA_LV_PREFIX]),
374     (False, _CLUSTER_VERIFY),
375     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
376     (True, _CLUSTER_VERIFY),
377     (False, ["lvremove", "-f", lvfullname]),
378     (False, _CLUSTER_VERIFY),
379     ]:
380     AssertCommand(cmd, fail=fail)
381
382
383 def TestClusterModifyEmpty():
384   """gnt-cluster modify"""
385   AssertCommand(["gnt-cluster", "modify"], fail=True)
386
387
388 def TestClusterModifyDisk():
389   """gnt-cluster modify -D"""
390   for param in _FAIL_PARAMS:
391     AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
392
393
394 def TestClusterModifyDiskTemplates():
395   """gnt-cluster modify --enabled-disk-templates=..."""
396   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
397   default_disk_template = qa_config.GetDefaultDiskTemplate()
398
399   _TestClusterModifyDiskTemplatesArguments(default_disk_template,
400                                            enabled_disk_templates)
401
402   _RestoreEnabledDiskTemplates()
403   nodes = qa_config.AcquireManyNodes(2)
404
405   instance_template = enabled_disk_templates[0]
406   instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
407
408   _TestClusterModifyUnusedDiskTemplate(instance_template)
409   _TestClusterModifyUsedDiskTemplate(instance_template,
410                                      enabled_disk_templates)
411
412   qa_instance.TestInstanceRemove(instance)
413   _RestoreEnabledDiskTemplates()
414
415
416 def _RestoreEnabledDiskTemplates():
417   """Sets the list of enabled disk templates back to the list of enabled disk
418      templates from the QA configuration. This can be used to make sure that
419      the tests that modify the list of disk templates do not interfere with
420      other tests.
421
422   """
423   AssertCommand(
424     ["gnt-cluster", "modify",
425      "--enabled-disk-template=%s" %
426        ",".join(qa_config.GetEnabledDiskTemplates())],
427     fail=False)
428
429
430 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
431                                              enabled_disk_templates):
432   """Tests argument handling of 'gnt-cluster modify' with respect to
433      the parameter '--enabled-disk-templates'. This test is independent
434      of instances.
435
436   """
437   AssertCommand(
438     ["gnt-cluster", "modify",
439      "--enabled-disk-template=%s" %
440        ",".join(enabled_disk_templates)],
441     fail=False)
442
443   # bogus templates
444   AssertCommand(["gnt-cluster", "modify",
445                  "--enabled-disk-templates=pinkbunny"],
446                 fail=True)
447
448   # duplicate entries do no harm
449   AssertCommand(
450     ["gnt-cluster", "modify",
451      "--enabled-disk-templates=%s,%s" %
452       (default_disk_template, default_disk_template)],
453     fail=False)
454
455
456 def _TestClusterModifyUsedDiskTemplate(instance_template,
457                                        enabled_disk_templates):
458   """Tests that disk templates that are currently in use by instances cannot
459      be disabled on the cluster.
460
461   """
462   # If the list of enabled disk templates contains only one template
463   # we need to add some other templates, because the list of enabled disk
464   # templates can only be set to a non-empty list.
465   new_disk_templates = list(set(enabled_disk_templates)
466                               - set([instance_template]))
467   if not new_disk_templates:
468     new_disk_templates = list(set(constants.DISK_TEMPLATES)
469                                 - set([instance_template]))
470   AssertCommand(
471     ["gnt-cluster", "modify",
472      "--enabled-disk-templates=%s" %
473        ",".join(new_disk_templates)],
474     fail=True)
475
476
477 def _TestClusterModifyUnusedDiskTemplate(instance_template):
478   """Tests that unused disk templates can be disabled safely."""
479   all_disk_templates = constants.DISK_TEMPLATES
480   AssertCommand(
481     ["gnt-cluster", "modify",
482      "--enabled-disk-templates=%s" %
483        ",".join(all_disk_templates)],
484     fail=False)
485   new_disk_templates = [instance_template]
486   AssertCommand(
487     ["gnt-cluster", "modify",
488      "--enabled-disk-templates=%s" %
489        ",".join(new_disk_templates)],
490     fail=False)
491
492
493 def TestClusterModifyBe():
494   """gnt-cluster modify -B"""
495   for fail, cmd in [
496     # max/min mem
497     (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
498     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
499     (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
500     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
501     (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
502     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
503     (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
504     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
505     (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
506     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
507     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
508     # vcpus
509     (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
510     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
511     (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
512     (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
513     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
514     # auto_balance
515     (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
516     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
517     (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
518     (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
519     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
520     ]:
521     AssertCommand(cmd, fail=fail)
522
523   # redo the original-requested BE parameters, if any
524   bep = qa_config.get("backend-parameters", "")
525   if bep:
526     AssertCommand(["gnt-cluster", "modify", "-B", bep])
527
528
529 def _GetClusterIPolicy():
530   """Return the run-time values of the cluster-level instance policy.
531
532   @rtype: tuple
533   @return: (policy, specs), where:
534       - policy is a dictionary of the policy values, instance specs excluded
535       - specs is a dictionary containing only the specs, using the internal
536         format (see L{constants.IPOLICY_DEFAULTS} for an example)
537
538   """
539   info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
540   policy = info["Instance policy - limits for instances"]
541   (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
542
543   # Sanity checks
544   assert "minmax" in ret_specs and "std" in ret_specs
545   assert len(ret_specs["minmax"]) > 0
546   assert len(ret_policy) > 0
547   return (ret_policy, ret_specs)
548
549
550 def TestClusterModifyIPolicy():
551   """gnt-cluster modify --ipolicy-*"""
552   basecmd = ["gnt-cluster", "modify"]
553   (old_policy, old_specs) = _GetClusterIPolicy()
554   for par in ["vcpu-ratio", "spindle-ratio"]:
555     curr_val = float(old_policy[par])
556     test_values = [
557       (True, 1.0),
558       (True, 1.5),
559       (True, 2),
560       (False, "a"),
561       # Restore the old value
562       (True, curr_val),
563       ]
564     for (good, val) in test_values:
565       cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
566       AssertCommand(cmd, fail=not good)
567       if good:
568         curr_val = val
569       # Check the affected parameter
570       (eff_policy, eff_specs) = _GetClusterIPolicy()
571       AssertEqual(float(eff_policy[par]), curr_val)
572       # Check everything else
573       AssertEqual(eff_specs, old_specs)
574       for p in eff_policy.keys():
575         if p == par:
576           continue
577         AssertEqual(eff_policy[p], old_policy[p])
578
579   # Disk templates are treated slightly differently
580   par = "disk-templates"
581   disp_str = "allowed disk templates"
582   curr_val = old_policy[disp_str]
583   test_values = [
584     (True, constants.DT_PLAIN),
585     (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
586     (False, "thisisnotadisktemplate"),
587     (False, ""),
588     # Restore the old value
589     (True, curr_val.replace(" ", "")),
590     ]
591   for (good, val) in test_values:
592     cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
593     AssertCommand(cmd, fail=not good)
594     if good:
595       curr_val = val
596     # Check the affected parameter
597     (eff_policy, eff_specs) = _GetClusterIPolicy()
598     AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
599     # Check everything else
600     AssertEqual(eff_specs, old_specs)
601     for p in eff_policy.keys():
602       if p == disp_str:
603         continue
604       AssertEqual(eff_policy[p], old_policy[p])
605
606
607 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
608                          old_values=None):
609   """Change instance specs.
610
611   At most one of new_specs or diff_specs can be specified.
612
613   @type new_specs: dict
614   @param new_specs: new complete specs, in the same format returned by
615       L{_GetClusterIPolicy}
616   @type diff_specs: dict
617   @param diff_specs: partial specs, it can be an incomplete specifications, but
618       if min/max specs are specified, their number must match the number of the
619       existing specs
620   @type fail: bool
621   @param fail: if the change is expected to fail
622   @type old_values: tuple
623   @param old_values: (old_policy, old_specs), as returned by
624       L{_GetClusterIPolicy}
625   @return: same as L{_GetClusterIPolicy}
626
627   """
628   build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
629   return qa_utils.TestSetISpecs(
630     new_specs=new_specs, diff_specs=diff_specs,
631     get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
632     fail=fail, old_values=old_values)
633
634
635 def TestClusterModifyISpecs():
636   """gnt-cluster modify --specs-*"""
637   params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
638   (cur_policy, cur_specs) = _GetClusterIPolicy()
639   # This test assumes that there is only one min/max bound
640   assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
641   for par in params:
642     test_values = [
643       (True, 0, 4, 12),
644       (True, 4, 4, 12),
645       (True, 4, 12, 12),
646       (True, 4, 4, 4),
647       (False, 4, 0, 12),
648       (False, 4, 16, 12),
649       (False, 4, 4, 0),
650       (False, 12, 4, 4),
651       (False, 12, 4, 0),
652       (False, "a", 4, 12),
653       (False, 0, "a", 12),
654       (False, 0, 4, "a"),
655       # This is to restore the old values
656       (True,
657        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
658        cur_specs[constants.ISPECS_STD][par],
659        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
660       ]
661     for (good, mn, st, mx) in test_values:
662       new_vals = {
663         constants.ISPECS_MINMAX: [{
664           constants.ISPECS_MIN: {par: mn},
665           constants.ISPECS_MAX: {par: mx}
666           }],
667         constants.ISPECS_STD: {par: st}
668         }
669       cur_state = (cur_policy, cur_specs)
670       # We update cur_specs, as we've copied the values to restore already
671       (cur_policy, cur_specs) = TestClusterSetISpecs(
672         diff_specs=new_vals, fail=not good, old_values=cur_state)
673
674     # Get the ipolicy command
675     mnode = qa_config.GetMasterNode()
676     initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
677     modcmd = ["gnt-cluster", "modify"]
678     opts = initcmd.split()
679     assert opts[0:2] == ["gnt-cluster", "init"]
680     for k in range(2, len(opts) - 1):
681       if opts[k].startswith("--ipolicy-"):
682         assert k + 2 <= len(opts)
683         modcmd.extend(opts[k:k + 2])
684     # Re-apply the ipolicy (this should be a no-op)
685     AssertCommand(modcmd)
686     new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
687     AssertEqual(initcmd, new_initcmd)
688
689
690 def TestClusterInfo():
691   """gnt-cluster info"""
692   AssertCommand(["gnt-cluster", "info"])
693
694
695 def TestClusterRedistConf():
696   """gnt-cluster redist-conf"""
697   AssertCommand(["gnt-cluster", "redist-conf"])
698
699
700 def TestClusterGetmaster():
701   """gnt-cluster getmaster"""
702   AssertCommand(["gnt-cluster", "getmaster"])
703
704
705 def TestClusterVersion():
706   """gnt-cluster version"""
707   AssertCommand(["gnt-cluster", "version"])
708
709
710 def TestClusterRenewCrypto():
711   """gnt-cluster renew-crypto"""
712   master = qa_config.GetMasterNode()
713
714   # Conflicting options
715   cmd = ["gnt-cluster", "renew-crypto", "--force",
716          "--new-cluster-certificate", "--new-confd-hmac-key"]
717   conflicting = [
718     ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
719     ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
720     ]
721   for i in conflicting:
722     AssertCommand(cmd + i, fail=True)
723
724   # Invalid RAPI certificate
725   cmd = ["gnt-cluster", "renew-crypto", "--force",
726          "--rapi-certificate=/dev/null"]
727   AssertCommand(cmd, fail=True)
728
729   rapi_cert_backup = qa_utils.BackupFile(master.primary,
730                                          pathutils.RAPI_CERT_FILE)
731   try:
732     # Custom RAPI certificate
733     fh = tempfile.NamedTemporaryFile()
734
735     # Ensure certificate doesn't cause "gnt-cluster verify" to complain
736     validity = constants.SSL_CERT_EXPIRATION_WARN * 3
737
738     utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
739
740     tmpcert = qa_utils.UploadFile(master.primary, fh.name)
741     try:
742       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
743                      "--rapi-certificate=%s" % tmpcert])
744     finally:
745       AssertCommand(["rm", "-f", tmpcert])
746
747     # Custom cluster domain secret
748     cds_fh = tempfile.NamedTemporaryFile()
749     cds_fh.write(utils.GenerateSecret())
750     cds_fh.write("\n")
751     cds_fh.flush()
752
753     tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
754     try:
755       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
756                      "--cluster-domain-secret=%s" % tmpcds])
757     finally:
758       AssertCommand(["rm", "-f", tmpcds])
759
760     # Normal case
761     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
762                    "--new-cluster-certificate", "--new-confd-hmac-key",
763                    "--new-rapi-certificate", "--new-cluster-domain-secret"])
764
765     # Restore RAPI certificate
766     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
767                    "--rapi-certificate=%s" % rapi_cert_backup])
768   finally:
769     AssertCommand(["rm", "-f", rapi_cert_backup])
770
771
772 def TestClusterBurnin():
773   """Burnin"""
774   master = qa_config.GetMasterNode()
775
776   options = qa_config.get("options", {})
777   disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
778   parallel = options.get("burnin-in-parallel", False)
779   check_inst = options.get("burnin-check-instances", False)
780   do_rename = options.get("burnin-rename", "")
781   do_reboot = options.get("burnin-reboot", True)
782   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
783
784   # Get as many instances as we need
785   instances = []
786   try:
787     try:
788       num = qa_config.get("options", {}).get("burnin-instances", 1)
789       for _ in range(0, num):
790         instances.append(qa_config.AcquireInstance())
791     except qa_error.OutOfInstancesError:
792       print "Not enough instances, continuing anyway."
793
794     if len(instances) < 1:
795       raise qa_error.Error("Burnin needs at least one instance")
796
797     script = qa_utils.UploadFile(master.primary, "../tools/burnin")
798     try:
799       disks = qa_config.GetDiskOptions()
800       # Run burnin
801       cmd = [script,
802              "--os=%s" % qa_config.get("os"),
803              "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
804              "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
805              "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
806              "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
807              "--disk-template=%s" % disk_template]
808       if parallel:
809         cmd.append("--parallel")
810         cmd.append("--early-release")
811       if check_inst:
812         cmd.append("--http-check")
813       if do_rename:
814         cmd.append("--rename=%s" % do_rename)
815       if not do_reboot:
816         cmd.append("--no-reboot")
817       else:
818         cmd.append("--reboot-types=%s" % ",".join(reboot_types))
819       cmd += [inst.name for inst in instances]
820       AssertCommand(cmd)
821     finally:
822       AssertCommand(["rm", "-f", script])
823
824   finally:
825     for inst in instances:
826       inst.Release()
827
828
829 def TestClusterMasterFailover():
830   """gnt-cluster master-failover"""
831   master = qa_config.GetMasterNode()
832   failovermaster = qa_config.AcquireNode(exclude=master)
833
834   cmd = ["gnt-cluster", "master-failover"]
835   node_list_cmd = ["gnt-node", "list"]
836   try:
837     AssertCommand(cmd, node=failovermaster)
838     AssertCommand(node_list_cmd, node=failovermaster)
839     # Back to original master node
840     AssertCommand(cmd, node=master)
841     AssertCommand(node_list_cmd, node=master)
842   finally:
843     failovermaster.Release()
844
845
846 def _NodeQueueDrainFile(node):
847   """Returns path to queue drain file for a node.
848
849   """
850   return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
851
852
853 def _AssertDrainFile(node, **kwargs):
854   """Checks for the queue drain file.
855
856   """
857   AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
858
859
860 def TestClusterMasterFailoverWithDrainedQueue():
861   """gnt-cluster master-failover with drained queue"""
862   master = qa_config.GetMasterNode()
863   failovermaster = qa_config.AcquireNode(exclude=master)
864
865   # Ensure queue is not drained
866   for node in [master, failovermaster]:
867     _AssertDrainFile(node, fail=True)
868
869   # Drain queue on failover master
870   AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
871                 node=failovermaster)
872
873   cmd = ["gnt-cluster", "master-failover"]
874   try:
875     _AssertDrainFile(failovermaster)
876     AssertCommand(cmd, node=failovermaster)
877     _AssertDrainFile(master, fail=True)
878     _AssertDrainFile(failovermaster, fail=True)
879
880     # Back to original master node
881     AssertCommand(cmd, node=master)
882   finally:
883     failovermaster.Release()
884
885   # Ensure queue is not drained
886   for node in [master, failovermaster]:
887     _AssertDrainFile(node, fail=True)
888
889
890 def TestClusterCopyfile():
891   """gnt-cluster copyfile"""
892   master = qa_config.GetMasterNode()
893
894   uniqueid = utils.NewUUID()
895
896   # Create temporary file
897   f = tempfile.NamedTemporaryFile()
898   f.write(uniqueid)
899   f.flush()
900   f.seek(0)
901
902   # Upload file to master node
903   testname = qa_utils.UploadFile(master.primary, f.name)
904   try:
905     # Copy file to all nodes
906     AssertCommand(["gnt-cluster", "copyfile", testname])
907     _CheckFileOnAllNodes(testname, uniqueid)
908   finally:
909     _RemoveFileFromAllNodes(testname)
910
911
912 def TestClusterCommand():
913   """gnt-cluster command"""
914   uniqueid = utils.NewUUID()
915   rfile = "/tmp/gnt%s" % utils.NewUUID()
916   rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
917   cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
918                               "%s >%s" % (rcmd, rfile)])
919
920   try:
921     AssertCommand(cmd)
922     _CheckFileOnAllNodes(rfile, uniqueid)
923   finally:
924     _RemoveFileFromAllNodes(rfile)
925
926
927 def TestClusterDestroy():
928   """gnt-cluster destroy"""
929   AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
930
931
932 def TestClusterRepairDiskSizes():
933   """gnt-cluster repair-disk-sizes"""
934   AssertCommand(["gnt-cluster", "repair-disk-sizes"])
935
936
937 def TestSetExclStorCluster(newvalue):
938   """Set the exclusive_storage node parameter at the cluster level.
939
940   @type newvalue: bool
941   @param newvalue: New value of exclusive_storage
942   @rtype: bool
943   @return: The old value of exclusive_storage
944
945   """
946   es_path = ["Default node parameters", "exclusive_storage"]
947   oldvalue = _GetClusterField(es_path)
948   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
949                  "exclusive_storage=%s" % newvalue])
950   effvalue = _GetClusterField(es_path)
951   if effvalue != newvalue:
952     raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
953                          " of %s" % (effvalue, newvalue))
954   qa_config.SetExclusiveStorage(newvalue)
955   return oldvalue
956
957
958 def TestExclStorSharedPv(node):
959   """cluster-verify reports LVs that share the same PV with exclusive_storage.
960
961   """
962   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
963   lvname1 = _QA_LV_PREFIX + "vol1"
964   lvname2 = _QA_LV_PREFIX + "vol2"
965   node_name = node.primary
966   AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
967   AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
968   AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
969   AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
970                                          constants.CV_ENODEORPHANLV])
971   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
972   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
973   AssertClusterVerify()