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