QA: fix file storage QA wrt ipolicy
[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   # if no lvm-based templates are supported, skip the test
411   if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
412     return
413   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
414   lvname = _QA_LV_PREFIX + "test"
415   lvfullname = "/".join([vgname, lvname])
416   for fail, cmd in [
417     (False, _CLUSTER_VERIFY),
418     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
419     (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
420     (True, _CLUSTER_VERIFY),
421     (False, ["gnt-cluster", "modify", "--reserved-lvs",
422              "%s,.*/other-test" % lvfullname]),
423     (False, _CLUSTER_VERIFY),
424     (False, ["gnt-cluster", "modify", "--reserved-lvs",
425              ".*/%s.*" % _QA_LV_PREFIX]),
426     (False, _CLUSTER_VERIFY),
427     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
428     (True, _CLUSTER_VERIFY),
429     (False, ["lvremove", "-f", lvfullname]),
430     (False, _CLUSTER_VERIFY),
431     ]:
432     AssertCommand(cmd, fail=fail)
433
434
435 def TestClusterModifyEmpty():
436   """gnt-cluster modify"""
437   AssertCommand(["gnt-cluster", "modify"], fail=True)
438
439
440 def TestClusterModifyDisk():
441   """gnt-cluster modify -D"""
442   for param in _FAIL_PARAMS:
443     AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
444
445
446 def _GetOtherEnabledDiskTemplate(undesired_disk_templates,
447                                  enabled_disk_templates):
448   """Returns one template that is not in the undesired set.
449
450   @type undesired_disk_templates: list of string
451   @param undesired_disk_templates: a list of disk templates that we want to
452       exclude when drawing one disk template from the list of enabled
453       disk templates
454   @type enabled_disk_templates: list of string
455   @param enabled_disk_templates: list of enabled disk templates (in QA)
456
457   """
458   desired_templates = list(set(enabled_disk_templates)
459                                 - set(undesired_disk_templates))
460   if desired_templates:
461     template = desired_templates[0]
462   else:
463     # If no desired disk template is available for QA, choose 'diskless' and
464     # hope for the best.
465     template = constants.ST_DISKLESS
466
467   return template
468
469
470 def TestClusterModifyFileBasedStorageDir(
471     file_disk_template, dir_config_key, default_dir, option_name):
472   """Tests gnt-cluster modify wrt to file-based directory options.
473
474   @type file_disk_template: string
475   @param file_disk_template: file-based disk template
476   @type dir_config_key: string
477   @param dir_config_key: key for the QA config to retrieve the default
478      directory value
479   @type default_dir: string
480   @param default_dir: default directory, if the QA config does not specify
481      it
482   @type option_name: string
483   @param option_name: name of the option of 'gnt-cluster modify' to
484      change the directory
485
486   """
487   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
488   assert file_disk_template in [constants.DT_FILE, constants.DT_SHARED_FILE]
489   if not qa_config.IsTemplateSupported(file_disk_template):
490     return
491
492   # Get some non-file-based disk template to disable file storage
493   other_disk_template = _GetOtherEnabledDiskTemplate(
494       utils.storage.GetDiskTemplatesOfStorageType(constants.ST_FILE),
495       enabled_disk_templates)
496
497   file_storage_dir = qa_config.get(dir_config_key, default_dir)
498   invalid_file_storage_dir = "/boot/"
499
500   for fail, cmd in [
501     (False, ["gnt-cluster", "modify",
502             "--enabled-disk-templates=%s" % file_disk_template,
503             "--ipolicy-disk-templates=%s" % file_disk_template]),
504     (False, ["gnt-cluster", "modify",
505             "--%s=%s" % (option_name, file_storage_dir)]),
506     (False, ["gnt-cluster", "modify",
507             "--%s=%s" % (option_name, invalid_file_storage_dir)]),
508     # file storage dir is set to an inacceptable path, thus verify
509     # should fail
510     (True, ["gnt-cluster", "verify"]),
511     # unsetting the storage dir while file storage is enabled
512     # should fail
513     (True, ["gnt-cluster", "modify",
514             "--%s=" % option_name]),
515     (False, ["gnt-cluster", "modify",
516             "--%s=%s" % (option_name, file_storage_dir)]),
517     (False, ["gnt-cluster", "modify",
518             "--enabled-disk-templates=%s" % other_disk_template,
519             "--ipolicy-disk-templates=%s" % other_disk_template]),
520     (False, ["gnt-cluster", "modify",
521             "--%s=%s" % (option_name, invalid_file_storage_dir)]),
522     # file storage is set to an inacceptable path, but file storage
523     # is disabled, thus verify should not fail
524     (False, ["gnt-cluster", "verify"]),
525     # unsetting the file storage dir while file storage is not enabled
526     # should be fine
527     (False, ["gnt-cluster", "modify",
528             "--%s=" % option_name]),
529     # resetting everything to sane values
530     (False, ["gnt-cluster", "modify",
531             "--%s=%s" % (option_name, file_storage_dir),
532             "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
533             "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates)])
534     ]:
535     AssertCommand(cmd, fail=fail)
536
537
538 def TestClusterModifyFileStorageDir():
539   """gnt-cluster modify --file-storage-dir=..."""
540   TestClusterModifyFileBasedStorageDir(
541       constants.DT_FILE, "default-file-storage-dir",
542       pathutils.DEFAULT_FILE_STORAGE_DIR,
543       "file-storage-dir")
544
545
546 def TestClusterModifySharedFileStorageDir():
547   """gnt-cluster modify --shared-file-storage-dir=..."""
548   TestClusterModifyFileBasedStorageDir(
549       constants.DT_SHARED_FILE, "default-shared-file-storage-dir",
550       pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
551       "shared-file-storage-dir")
552
553
554 def TestClusterModifyDiskTemplates():
555   """gnt-cluster modify --enabled-disk-templates=..."""
556   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
557   default_disk_template = qa_config.GetDefaultDiskTemplate()
558
559   _TestClusterModifyDiskTemplatesArguments(default_disk_template,
560                                            enabled_disk_templates)
561   _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
562
563   _RestoreEnabledDiskTemplates()
564   nodes = qa_config.AcquireManyNodes(2)
565
566   instance_template = enabled_disk_templates[0]
567   instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
568
569   _TestClusterModifyUnusedDiskTemplate(instance_template)
570   _TestClusterModifyUsedDiskTemplate(instance_template,
571                                      enabled_disk_templates)
572
573   qa_instance.TestInstanceRemove(instance)
574   _RestoreEnabledDiskTemplates()
575
576
577 def _RestoreEnabledDiskTemplates():
578   """Sets the list of enabled disk templates back to the list of enabled disk
579      templates from the QA configuration. This can be used to make sure that
580      the tests that modify the list of disk templates do not interfere with
581      other tests.
582
583   """
584   enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
585   cmd = ["gnt-cluster", "modify",
586          "--enabled-disk-templates=%s" % ",".join(enabled_disk_templates),
587          "--ipolicy-disk-templates=%s" % ",".join(enabled_disk_templates),
588          ]
589
590   if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
591     vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
592     cmd.append("--vg-name=%s" % vgname)
593
594   AssertCommand(cmd, fail=False)
595
596
597 def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
598                                              enabled_disk_templates):
599   """Tests argument handling of 'gnt-cluster modify' with respect to
600      the parameter '--enabled-disk-templates'. This test is independent
601      of instances.
602
603   """
604   _RestoreEnabledDiskTemplates()
605
606   # bogus templates
607   AssertCommand(["gnt-cluster", "modify",
608                  "--enabled-disk-templates=pinkbunny"],
609                 fail=True)
610
611   # duplicate entries do no harm
612   AssertCommand(
613     ["gnt-cluster", "modify",
614      "--enabled-disk-templates=%s,%s" %
615       (default_disk_template, default_disk_template),
616      "--ipolicy-disk-templates=%s" % default_disk_template],
617     fail=False)
618
619   if constants.DT_DRBD8 in enabled_disk_templates:
620     # interaction with --drbd-usermode-helper option
621     drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
622     if not drbd_usermode_helper:
623       drbd_usermode_helper = "/bin/true"
624     # specifying a helper when drbd gets disabled is ok. Note that drbd still
625     # has to be installed on the nodes in this case
626     AssertCommand(["gnt-cluster", "modify",
627                    "--drbd-usermode-helper=%s" % drbd_usermode_helper,
628                    "--enabled-disk-templates=%s" % constants.DT_DISKLESS,
629                    "--ipolicy-disk-templates=%s" % constants.DT_DISKLESS],
630                    fail=False)
631     # specifying a helper when drbd is re-enabled
632     AssertCommand(["gnt-cluster", "modify",
633                    "--drbd-usermode-helper=%s" % drbd_usermode_helper,
634                    "--enabled-disk-templates=%s" %
635                      ",".join(enabled_disk_templates),
636                    "--ipolicy-disk-templates=%s" %
637                      ",".join(enabled_disk_templates)],
638                   fail=False)
639
640
641 def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
642   """Tests argument handling of 'gnt-cluster modify' with respect to
643      the parameter '--enabled-disk-templates' and '--vg-name'. This test is
644      independent of instances.
645
646   """
647   if not utils.IsLvmEnabled(enabled_disk_templates):
648     # These tests only make sense if lvm is enabled for QA
649     return
650
651   # determine an LVM and a non-LVM disk template for the tests
652   non_lvm_template = _GetOtherEnabledDiskTemplate(utils.GetLvmDiskTemplates(),
653                                                   enabled_disk_templates)
654   lvm_template = list(set(enabled_disk_templates)
655                       .intersection(set(utils.GetLvmDiskTemplates())))[0]
656
657   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
658
659   # Clean start: unset volume group name, disable lvm storage
660   AssertCommand(
661     ["gnt-cluster", "modify",
662      "--enabled-disk-templates=%s" % non_lvm_template,
663      "--ipolicy-disk-templates=%s" % non_lvm_template,
664      "--vg-name="],
665     fail=False)
666
667   # Try to enable lvm, when no volume group is given
668   AssertCommand(
669     ["gnt-cluster", "modify",
670      "--enabled-disk-templates=%s" % lvm_template,
671      "--ipolicy-disk-templates=%s" % lvm_template],
672     fail=True)
673
674   # Set volume group, with lvm still disabled: just a warning
675   AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
676
677   # Try unsetting vg name and enabling lvm at the same time
678   AssertCommand(
679     ["gnt-cluster", "modify",
680      "--enabled-disk-templates=%s" % lvm_template,
681      "--ipolicy-disk-templates=%s" % lvm_template,
682      "--vg-name="],
683     fail=True)
684
685   # Enable lvm with vg name present
686   AssertCommand(
687     ["gnt-cluster", "modify",
688      "--enabled-disk-templates=%s" % lvm_template,
689      "--ipolicy-disk-templates=%s" % lvm_template],
690     fail=False)
691
692   # Try unsetting vg name with lvm still enabled
693   AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
694
695   # Disable lvm with vg name still set
696   AssertCommand(
697     ["gnt-cluster", "modify",
698      "--enabled-disk-templates=%s" % non_lvm_template,
699      "--ipolicy-disk-templates=%s" % non_lvm_template,
700      ],
701     fail=False)
702
703   # Try unsetting vg name with lvm disabled
704   AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
705
706   # Set vg name and enable lvm at the same time
707   AssertCommand(
708     ["gnt-cluster", "modify",
709      "--enabled-disk-templates=%s" % lvm_template,
710      "--ipolicy-disk-templates=%s" % lvm_template,
711      "--vg-name=%s" % vgname],
712     fail=False)
713
714   # Unset vg name and disable lvm at the same time
715   AssertCommand(
716     ["gnt-cluster", "modify",
717      "--enabled-disk-templates=%s" % non_lvm_template,
718      "--ipolicy-disk-templates=%s" % non_lvm_template,
719      "--vg-name="],
720     fail=False)
721
722   _RestoreEnabledDiskTemplates()
723
724
725 def _TestClusterModifyUsedDiskTemplate(instance_template,
726                                        enabled_disk_templates):
727   """Tests that disk templates that are currently in use by instances cannot
728      be disabled on the cluster.
729
730   """
731   # If the list of enabled disk templates contains only one template
732   # we need to add some other templates, because the list of enabled disk
733   # templates can only be set to a non-empty list.
734   new_disk_templates = list(set(enabled_disk_templates)
735                               - set([instance_template]))
736   if not new_disk_templates:
737     new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
738                                 - set([instance_template]))
739   AssertCommand(
740     ["gnt-cluster", "modify",
741      "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
742      "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
743     fail=True)
744
745
746 def _TestClusterModifyUnusedDiskTemplate(instance_template):
747   """Tests that unused disk templates can be disabled safely."""
748   all_disk_templates = constants.DISK_TEMPLATES
749   if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
750     all_disk_templates = list(set(all_disk_templates) -
751                               set(utils.GetLvmDiskTemplates()))
752
753   AssertCommand(
754     ["gnt-cluster", "modify",
755      "--enabled-disk-templates=%s" % ",".join(all_disk_templates),
756      "--ipolicy-disk-templates=%s" % ",".join(all_disk_templates)],
757     fail=False)
758   new_disk_templates = [instance_template]
759   AssertCommand(
760     ["gnt-cluster", "modify",
761      "--enabled-disk-templates=%s" % ",".join(new_disk_templates),
762      "--ipolicy-disk-templates=%s" % ",".join(new_disk_templates)],
763     fail=False)
764
765
766 def TestClusterModifyBe():
767   """gnt-cluster modify -B"""
768   for fail, cmd in [
769     # max/min mem
770     (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
771     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
772     (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
773     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
774     (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
775     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
776     (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
777     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
778     (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
779     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
780     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
781     # vcpus
782     (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
783     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
784     (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
785     (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
786     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
787     # auto_balance
788     (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
789     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
790     (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
791     (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
792     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
793     ]:
794     AssertCommand(cmd, fail=fail)
795
796   # redo the original-requested BE parameters, if any
797   bep = qa_config.get("backend-parameters", "")
798   if bep:
799     AssertCommand(["gnt-cluster", "modify", "-B", bep])
800
801
802 def _GetClusterIPolicy():
803   """Return the run-time values of the cluster-level instance policy.
804
805   @rtype: tuple
806   @return: (policy, specs), where:
807       - policy is a dictionary of the policy values, instance specs excluded
808       - specs is a dictionary containing only the specs, using the internal
809         format (see L{constants.IPOLICY_DEFAULTS} for an example)
810
811   """
812   info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
813   policy = info["Instance policy - limits for instances"]
814   (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
815
816   # Sanity checks
817   assert "minmax" in ret_specs and "std" in ret_specs
818   assert len(ret_specs["minmax"]) > 0
819   assert len(ret_policy) > 0
820   return (ret_policy, ret_specs)
821
822
823 def TestClusterModifyIPolicy():
824   """gnt-cluster modify --ipolicy-*"""
825   basecmd = ["gnt-cluster", "modify"]
826   (old_policy, old_specs) = _GetClusterIPolicy()
827   for par in ["vcpu-ratio", "spindle-ratio"]:
828     curr_val = float(old_policy[par])
829     test_values = [
830       (True, 1.0),
831       (True, 1.5),
832       (True, 2),
833       (False, "a"),
834       # Restore the old value
835       (True, curr_val),
836       ]
837     for (good, val) in test_values:
838       cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
839       AssertCommand(cmd, fail=not good)
840       if good:
841         curr_val = val
842       # Check the affected parameter
843       (eff_policy, eff_specs) = _GetClusterIPolicy()
844       AssertEqual(float(eff_policy[par]), curr_val)
845       # Check everything else
846       AssertEqual(eff_specs, old_specs)
847       for p in eff_policy.keys():
848         if p == par:
849           continue
850         AssertEqual(eff_policy[p], old_policy[p])
851
852   # Allowing disk templates via ipolicy requires them to be
853   # enabled on the cluster.
854   if not (qa_config.IsTemplateSupported(constants.DT_PLAIN)
855           and qa_config.IsTemplateSupported(constants.DT_DRBD8)):
856     return
857   # Disk templates are treated slightly differently
858   par = "disk-templates"
859   disp_str = "allowed disk templates"
860   curr_val = old_policy[disp_str]
861   test_values = [
862     (True, constants.DT_PLAIN),
863     (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
864     (False, "thisisnotadisktemplate"),
865     (False, ""),
866     # Restore the old value
867     (True, curr_val.replace(" ", "")),
868     ]
869   for (good, val) in test_values:
870     cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
871     AssertCommand(cmd, fail=not good)
872     if good:
873       curr_val = val
874     # Check the affected parameter
875     (eff_policy, eff_specs) = _GetClusterIPolicy()
876     AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
877     # Check everything else
878     AssertEqual(eff_specs, old_specs)
879     for p in eff_policy.keys():
880       if p == disp_str:
881         continue
882       AssertEqual(eff_policy[p], old_policy[p])
883
884
885 def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
886                          old_values=None):
887   """Change instance specs.
888
889   At most one of new_specs or diff_specs can be specified.
890
891   @type new_specs: dict
892   @param new_specs: new complete specs, in the same format returned by
893       L{_GetClusterIPolicy}
894   @type diff_specs: dict
895   @param diff_specs: partial specs, it can be an incomplete specifications, but
896       if min/max specs are specified, their number must match the number of the
897       existing specs
898   @type fail: bool
899   @param fail: if the change is expected to fail
900   @type old_values: tuple
901   @param old_values: (old_policy, old_specs), as returned by
902       L{_GetClusterIPolicy}
903   @return: same as L{_GetClusterIPolicy}
904
905   """
906   build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
907   return qa_utils.TestSetISpecs(
908     new_specs=new_specs, diff_specs=diff_specs,
909     get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
910     fail=fail, old_values=old_values)
911
912
913 def TestClusterModifyISpecs():
914   """gnt-cluster modify --specs-*"""
915   params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
916   (cur_policy, cur_specs) = _GetClusterIPolicy()
917   # This test assumes that there is only one min/max bound
918   assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
919   for par in params:
920     test_values = [
921       (True, 0, 4, 12),
922       (True, 4, 4, 12),
923       (True, 4, 12, 12),
924       (True, 4, 4, 4),
925       (False, 4, 0, 12),
926       (False, 4, 16, 12),
927       (False, 4, 4, 0),
928       (False, 12, 4, 4),
929       (False, 12, 4, 0),
930       (False, "a", 4, 12),
931       (False, 0, "a", 12),
932       (False, 0, 4, "a"),
933       # This is to restore the old values
934       (True,
935        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
936        cur_specs[constants.ISPECS_STD][par],
937        cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
938       ]
939     for (good, mn, st, mx) in test_values:
940       new_vals = {
941         constants.ISPECS_MINMAX: [{
942           constants.ISPECS_MIN: {par: mn},
943           constants.ISPECS_MAX: {par: mx}
944           }],
945         constants.ISPECS_STD: {par: st}
946         }
947       cur_state = (cur_policy, cur_specs)
948       # We update cur_specs, as we've copied the values to restore already
949       (cur_policy, cur_specs) = TestClusterSetISpecs(
950         diff_specs=new_vals, fail=not good, old_values=cur_state)
951
952     # Get the ipolicy command
953     mnode = qa_config.GetMasterNode()
954     initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
955     modcmd = ["gnt-cluster", "modify"]
956     opts = initcmd.split()
957     assert opts[0:2] == ["gnt-cluster", "init"]
958     for k in range(2, len(opts) - 1):
959       if opts[k].startswith("--ipolicy-"):
960         assert k + 2 <= len(opts)
961         modcmd.extend(opts[k:k + 2])
962     # Re-apply the ipolicy (this should be a no-op)
963     AssertCommand(modcmd)
964     new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
965     AssertEqual(initcmd, new_initcmd)
966
967
968 def TestClusterInfo():
969   """gnt-cluster info"""
970   AssertCommand(["gnt-cluster", "info"])
971
972
973 def TestClusterRedistConf():
974   """gnt-cluster redist-conf"""
975   AssertCommand(["gnt-cluster", "redist-conf"])
976
977
978 def TestClusterGetmaster():
979   """gnt-cluster getmaster"""
980   AssertCommand(["gnt-cluster", "getmaster"])
981
982
983 def TestClusterVersion():
984   """gnt-cluster version"""
985   AssertCommand(["gnt-cluster", "version"])
986
987
988 def TestClusterRenewCrypto():
989   """gnt-cluster renew-crypto"""
990   master = qa_config.GetMasterNode()
991
992   # Conflicting options
993   cmd = ["gnt-cluster", "renew-crypto", "--force",
994          "--new-cluster-certificate", "--new-confd-hmac-key"]
995   conflicting = [
996     ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
997     ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
998     ]
999   for i in conflicting:
1000     AssertCommand(cmd + i, fail=True)
1001
1002   # Invalid RAPI certificate
1003   cmd = ["gnt-cluster", "renew-crypto", "--force",
1004          "--rapi-certificate=/dev/null"]
1005   AssertCommand(cmd, fail=True)
1006
1007   rapi_cert_backup = qa_utils.BackupFile(master.primary,
1008                                          pathutils.RAPI_CERT_FILE)
1009   try:
1010     # Custom RAPI certificate
1011     fh = tempfile.NamedTemporaryFile()
1012
1013     # Ensure certificate doesn't cause "gnt-cluster verify" to complain
1014     validity = constants.SSL_CERT_EXPIRATION_WARN * 3
1015
1016     utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
1017
1018     tmpcert = qa_utils.UploadFile(master.primary, fh.name)
1019     try:
1020       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1021                      "--rapi-certificate=%s" % tmpcert])
1022     finally:
1023       AssertCommand(["rm", "-f", tmpcert])
1024
1025     # Custom cluster domain secret
1026     cds_fh = tempfile.NamedTemporaryFile()
1027     cds_fh.write(utils.GenerateSecret())
1028     cds_fh.write("\n")
1029     cds_fh.flush()
1030
1031     tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
1032     try:
1033       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1034                      "--cluster-domain-secret=%s" % tmpcds])
1035     finally:
1036       AssertCommand(["rm", "-f", tmpcds])
1037
1038     # Normal case
1039     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1040                    "--new-cluster-certificate", "--new-confd-hmac-key",
1041                    "--new-rapi-certificate", "--new-cluster-domain-secret"])
1042
1043     # Restore RAPI certificate
1044     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
1045                    "--rapi-certificate=%s" % rapi_cert_backup])
1046   finally:
1047     AssertCommand(["rm", "-f", rapi_cert_backup])
1048
1049
1050 def TestClusterBurnin():
1051   """Burnin"""
1052   master = qa_config.GetMasterNode()
1053
1054   options = qa_config.get("options", {})
1055   disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
1056   parallel = options.get("burnin-in-parallel", False)
1057   check_inst = options.get("burnin-check-instances", False)
1058   do_rename = options.get("burnin-rename", "")
1059   do_reboot = options.get("burnin-reboot", True)
1060   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
1061
1062   # Get as many instances as we need
1063   instances = []
1064   try:
1065     try:
1066       num = qa_config.get("options", {}).get("burnin-instances", 1)
1067       for _ in range(0, num):
1068         instances.append(qa_config.AcquireInstance())
1069     except qa_error.OutOfInstancesError:
1070       print "Not enough instances, continuing anyway."
1071
1072     if len(instances) < 1:
1073       raise qa_error.Error("Burnin needs at least one instance")
1074
1075     script = qa_utils.UploadFile(master.primary, "../tools/burnin")
1076     try:
1077       disks = qa_config.GetDiskOptions()
1078       # Run burnin
1079       cmd = [script,
1080              "--os=%s" % qa_config.get("os"),
1081              "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
1082              "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
1083              "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
1084              "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
1085              "--disk-template=%s" % disk_template]
1086       if parallel:
1087         cmd.append("--parallel")
1088         cmd.append("--early-release")
1089       if check_inst:
1090         cmd.append("--http-check")
1091       if do_rename:
1092         cmd.append("--rename=%s" % do_rename)
1093       if not do_reboot:
1094         cmd.append("--no-reboot")
1095       else:
1096         cmd.append("--reboot-types=%s" % ",".join(reboot_types))
1097       cmd += [inst.name for inst in instances]
1098       AssertCommand(cmd)
1099     finally:
1100       AssertCommand(["rm", "-f", script])
1101
1102   finally:
1103     for inst in instances:
1104       inst.Release()
1105
1106
1107 def TestClusterMasterFailover():
1108   """gnt-cluster master-failover"""
1109   master = qa_config.GetMasterNode()
1110   failovermaster = qa_config.AcquireNode(exclude=master)
1111
1112   cmd = ["gnt-cluster", "master-failover"]
1113   try:
1114     AssertCommand(cmd, node=failovermaster)
1115     # Back to original master node
1116     AssertCommand(cmd, node=master)
1117   finally:
1118     failovermaster.Release()
1119
1120
1121 def _NodeQueueDrainFile(node):
1122   """Returns path to queue drain file for a node.
1123
1124   """
1125   return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1126
1127
1128 def _AssertDrainFile(node, **kwargs):
1129   """Checks for the queue drain file.
1130
1131   """
1132   AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1133
1134
1135 def TestClusterMasterFailoverWithDrainedQueue():
1136   """gnt-cluster master-failover with drained queue"""
1137   master = qa_config.GetMasterNode()
1138   failovermaster = qa_config.AcquireNode(exclude=master)
1139
1140   # Ensure queue is not drained
1141   for node in [master, failovermaster]:
1142     _AssertDrainFile(node, fail=True)
1143
1144   # Drain queue on failover master
1145   AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1146                 node=failovermaster)
1147
1148   cmd = ["gnt-cluster", "master-failover"]
1149   try:
1150     _AssertDrainFile(failovermaster)
1151     AssertCommand(cmd, node=failovermaster)
1152     _AssertDrainFile(master, fail=True)
1153     _AssertDrainFile(failovermaster, fail=True)
1154
1155     # Back to original master node
1156     AssertCommand(cmd, node=master)
1157   finally:
1158     failovermaster.Release()
1159
1160   # Ensure queue is not drained
1161   for node in [master, failovermaster]:
1162     _AssertDrainFile(node, fail=True)
1163
1164
1165 def TestClusterCopyfile():
1166   """gnt-cluster copyfile"""
1167   master = qa_config.GetMasterNode()
1168
1169   uniqueid = utils.NewUUID()
1170
1171   # Create temporary file
1172   f = tempfile.NamedTemporaryFile()
1173   f.write(uniqueid)
1174   f.flush()
1175   f.seek(0)
1176
1177   # Upload file to master node
1178   testname = qa_utils.UploadFile(master.primary, f.name)
1179   try:
1180     # Copy file to all nodes
1181     AssertCommand(["gnt-cluster", "copyfile", testname])
1182     _CheckFileOnAllNodes(testname, uniqueid)
1183   finally:
1184     _RemoveFileFromAllNodes(testname)
1185
1186
1187 def TestClusterCommand():
1188   """gnt-cluster command"""
1189   uniqueid = utils.NewUUID()
1190   rfile = "/tmp/gnt%s" % utils.NewUUID()
1191   rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1192   cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1193                               "%s >%s" % (rcmd, rfile)])
1194
1195   try:
1196     AssertCommand(cmd)
1197     _CheckFileOnAllNodes(rfile, uniqueid)
1198   finally:
1199     _RemoveFileFromAllNodes(rfile)
1200
1201
1202 def TestClusterDestroy():
1203   """gnt-cluster destroy"""
1204   AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1205
1206
1207 def TestClusterRepairDiskSizes():
1208   """gnt-cluster repair-disk-sizes"""
1209   AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1210
1211
1212 def TestSetExclStorCluster(newvalue):
1213   """Set the exclusive_storage node parameter at the cluster level.
1214
1215   @type newvalue: bool
1216   @param newvalue: New value of exclusive_storage
1217   @rtype: bool
1218   @return: The old value of exclusive_storage
1219
1220   """
1221   es_path = ["Default node parameters", "exclusive_storage"]
1222   oldvalue = _GetClusterField(es_path)
1223   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1224                  "exclusive_storage=%s" % newvalue])
1225   effvalue = _GetClusterField(es_path)
1226   if effvalue != newvalue:
1227     raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1228                          " of %s" % (effvalue, newvalue))
1229   qa_config.SetExclusiveStorage(newvalue)
1230   return oldvalue
1231
1232
1233 def TestExclStorSharedPv(node):
1234   """cluster-verify reports LVs that share the same PV with exclusive_storage.
1235
1236   """
1237   vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1238   lvname1 = _QA_LV_PREFIX + "vol1"
1239   lvname2 = _QA_LV_PREFIX + "vol2"
1240   node_name = node.primary
1241   AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1242   AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1243   AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1244   AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1245                                          constants.CV_ENODEORPHANLV])
1246   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1247   AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1248   AssertClusterVerify()