QA: Uniformity check for exclusive_storage in cluster-verify
[ganeti-local] / qa / qa_cluster.py
1 #
2 #
3
4 # Copyright (C) 2007, 2010, 2011, 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Cluster related QA tests.
23
24 """
25
26 import re
27 import tempfile
28 import os.path
29
30 from ganeti import constants
31 from ganeti import compat
32 from ganeti import utils
33 from ganeti import pathutils
34
35 import qa_config
36 import qa_utils
37 import qa_error
38
39 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
40
41
42 #: cluster verify command
43 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
44
45
46 def _RemoveFileFromAllNodes(filename):
47   """Removes a file from all nodes.
48
49   """
50   for node in qa_config.get("nodes"):
51     AssertCommand(["rm", "-f", filename], node=node)
52
53
54 def _CheckFileOnAllNodes(filename, content):
55   """Verifies the content of the given file on all nodes.
56
57   """
58   cmd = utils.ShellQuoteArgs(["cat", filename])
59   for node in qa_config.get("nodes"):
60     AssertEqual(qa_utils.GetCommandOutput(node["primary"], cmd), content)
61
62
63 # "gnt-cluster info" fields
64 _CIFIELD_RE = re.compile(r"^[-\s]*(?P<field>[^\s:]+):\s*(?P<value>\S.*)$")
65
66
67 def _GetBoolClusterField(field):
68   """Get the Boolean value of a cluster field.
69
70   This function currently assumes that the field name is unique in the cluster
71   configuration. An assertion checks this assumption.
72
73   @type field: string
74   @param field: Name of the field
75   @rtype: bool
76   @return: The effective value of the field
77
78   """
79   master = qa_config.GetMasterNode()
80   infocmd = "gnt-cluster info"
81   info_out = qa_utils.GetCommandOutput(master["primary"], infocmd)
82   ret = None
83   for l in info_out.splitlines():
84     m = _CIFIELD_RE.match(l)
85     # FIXME: There should be a way to specify a field through a hierarchy
86     if m and m.group("field") == field:
87       # Make sure that ignoring the hierarchy doesn't cause a double match
88       assert ret is None
89       ret = (m.group("value").lower() == "true")
90   if ret is not None:
91     return ret
92   raise qa_error.Error("Field not found in cluster configuration: %s" % field)
93
94
95 # Cluster-verify errors (date, "ERROR", then error code)
96 _CVERROR_RE = re.compile(r"^[\w\s:]+\s+- ERROR:([A-Z0-9_-]+):")
97
98
99 def _GetCVErrorCodes(cvout):
100   ret = set()
101   for l in cvout.splitlines():
102     m = _CVERROR_RE.match(l)
103     if m:
104       ecode = m.group(1)
105       ret.add(ecode)
106   return ret
107
108
109 def AssertClusterVerify(fail=False, errors=None):
110   """Run cluster-verify and check the result
111
112   @type fail: bool
113   @param fail: if cluster-verify is expected to fail instead of succeeding
114   @type errors: list of tuples
115   @param errors: List of CV_XXX errors that are expected; if specified, all the
116       errors listed must appear in cluster-verify output. A non-empty value
117       implies C{fail=True}.
118
119   """
120   cvcmd = "gnt-cluster verify"
121   mnode = qa_config.GetMasterNode()
122   if errors:
123     cvout = GetCommandOutput(mnode["primary"], cvcmd + " --error-codes",
124                              fail=True)
125     actual = _GetCVErrorCodes(cvout)
126     expected = compat.UniqueFrozenset(e for (_, e, _) in errors)
127     if not actual.issuperset(expected):
128       missing = expected.difference(actual)
129       raise qa_error.Error("Cluster-verify didn't return these expected"
130                            " errors: %s" % utils.CommaJoin(missing))
131   else:
132     AssertCommand(cvcmd, fail=fail, node=mnode)
133
134
135 # data for testing failures due to bad keys/values for disk parameters
136 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
137                 "drbd:nonexistent=1",
138                 "drbd:resync-rate=invalid",
139                 ]
140
141
142 def TestClusterInitDisk():
143   """gnt-cluster init -D"""
144   name = qa_config.get("name")
145   for param in _FAIL_PARAMS:
146     AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
147
148
149 def TestClusterInit(rapi_user, rapi_secret):
150   """gnt-cluster init"""
151   master = qa_config.GetMasterNode()
152
153   rapi_dir = os.path.dirname(pathutils.RAPI_USERS_FILE)
154
155   # First create the RAPI credentials
156   fh = tempfile.NamedTemporaryFile()
157   try:
158     fh.write("%s %s write\n" % (rapi_user, rapi_secret))
159     fh.flush()
160
161     tmpru = qa_utils.UploadFile(master["primary"], fh.name)
162     try:
163       AssertCommand(["mkdir", "-p", rapi_dir])
164       AssertCommand(["mv", tmpru, pathutils.RAPI_USERS_FILE])
165     finally:
166       AssertCommand(["rm", "-f", tmpru])
167   finally:
168     fh.close()
169
170   # Initialize cluster
171   cmd = [
172     "gnt-cluster", "init",
173     "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
174     "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
175     ]
176
177   for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
178                     "nic-count"):
179     for spec_val in ("min", "max", "std"):
180       spec = qa_config.get("ispec_%s_%s" %
181                            (spec_type.replace("-", "_"), spec_val), None)
182       if spec:
183         cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
184
185   if master.get("secondary", None):
186     cmd.append("--secondary-ip=%s" % master["secondary"])
187
188   master_netdev = qa_config.get("master-netdev", None)
189   if master_netdev:
190     cmd.append("--master-netdev=%s" % master_netdev)
191
192   nicparams = qa_config.get("default-nicparams", None)
193   if nicparams:
194     cmd.append("--nic-parameters=%s" %
195                ",".join(utils.FormatKeyValue(nicparams)))
196
197   cmd.append(qa_config.get("name"))
198   AssertCommand(cmd)
199
200   cmd = ["gnt-cluster", "modify"]
201
202   # hypervisor parameter modifications
203   hvp = qa_config.get("hypervisor-parameters", {})
204   for k, v in hvp.items():
205     cmd.extend(["-H", "%s:%s" % (k, v)])
206   # backend parameter modifications
207   bep = qa_config.get("backend-parameters", "")
208   if bep:
209     cmd.extend(["-B", bep])
210
211   if len(cmd) > 2:
212     AssertCommand(cmd)
213
214   # OS parameters
215   osp = qa_config.get("os-parameters", {})
216   for k, v in osp.items():
217     AssertCommand(["gnt-os", "modify", "-O", v, k])
218
219   # OS hypervisor parameters
220   os_hvp = qa_config.get("os-hvp", {})
221   for os_name in os_hvp:
222     for hv, hvp in os_hvp[os_name].items():
223       AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
224
225
226 def TestClusterRename():
227   """gnt-cluster rename"""
228   cmd = ["gnt-cluster", "rename", "-f"]
229
230   original_name = qa_config.get("name")
231   rename_target = qa_config.get("rename", None)
232   if rename_target is None:
233     print qa_utils.FormatError('"rename" entry is missing')
234     return
235
236   for data in [
237     cmd + [rename_target],
238     _CLUSTER_VERIFY,
239     cmd + [original_name],
240     _CLUSTER_VERIFY,
241     ]:
242     AssertCommand(data)
243
244
245 def TestClusterOob():
246   """out-of-band framework"""
247   oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
248
249   AssertCommand(_CLUSTER_VERIFY)
250   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
251                  "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
252                  utils.NewUUID()])
253
254   AssertCommand(_CLUSTER_VERIFY, fail=True)
255
256   AssertCommand(["touch", oob_path_exists])
257   AssertCommand(["chmod", "0400", oob_path_exists])
258   AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
259
260   try:
261     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
262                    "oob_program=%s" % oob_path_exists])
263
264     AssertCommand(_CLUSTER_VERIFY, fail=True)
265
266     AssertCommand(["chmod", "0500", oob_path_exists])
267     AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
268
269     AssertCommand(_CLUSTER_VERIFY)
270   finally:
271     AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
272
273   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
274                  "oob_program="])
275
276
277 def TestClusterEpo():
278   """gnt-cluster epo"""
279   master = qa_config.GetMasterNode()
280
281   # Assert that OOB is unavailable for all nodes
282   result_output = GetCommandOutput(master["primary"],
283                                    "gnt-node list --verbose --no-headers -o"
284                                    " powered")
285   AssertEqual(compat.all(powered == "(unavail)"
286                          for powered in result_output.splitlines()), True)
287
288   # Conflicting
289   AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
290   # --all doesn't expect arguments
291   AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
292
293   # Unless --all is given master is not allowed to be in the list
294   AssertCommand(["gnt-cluster", "epo", "-f", master["primary"]], fail=True)
295
296   # This shouldn't fail
297   AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
298
299   # All instances should have been stopped now
300   result_output = GetCommandOutput(master["primary"],
301                                    "gnt-instance list --no-headers -o status")
302   # ERROR_down because the instance is stopped but not recorded as such
303   AssertEqual(compat.all(status == "ERROR_down"
304                          for status in result_output.splitlines()), True)
305
306   # Now start everything again
307   AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
308
309   # All instances should have been started now
310   result_output = GetCommandOutput(master["primary"],
311                                    "gnt-instance list --no-headers -o status")
312   AssertEqual(compat.all(status == "running"
313                          for status in result_output.splitlines()), True)
314
315
316 def TestClusterVerify():
317   """gnt-cluster verify"""
318   AssertCommand(_CLUSTER_VERIFY)
319   AssertCommand(["gnt-cluster", "verify-disks"])
320
321
322 def TestJobqueue():
323   """gnt-debug test-jobqueue"""
324   AssertCommand(["gnt-debug", "test-jobqueue"])
325
326
327 def TestDelay(node):
328   """gnt-debug delay"""
329   AssertCommand(["gnt-debug", "delay", "1"])
330   AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
331   AssertCommand(["gnt-debug", "delay", "--no-master",
332                  "-n", node["primary"], "1"])
333
334
335 def TestClusterReservedLvs():
336   """gnt-cluster reserved lvs"""
337   for fail, cmd in [
338     (False, _CLUSTER_VERIFY),
339     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
340     (False, ["lvcreate", "-L1G", "-nqa-test", "xenvg"]),
341     (True, _CLUSTER_VERIFY),
342     (False, ["gnt-cluster", "modify", "--reserved-lvs",
343              "xenvg/qa-test,.*/other-test"]),
344     (False, _CLUSTER_VERIFY),
345     (False, ["gnt-cluster", "modify", "--reserved-lvs", ".*/qa-.*"]),
346     (False, _CLUSTER_VERIFY),
347     (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
348     (True, _CLUSTER_VERIFY),
349     (False, ["lvremove", "-f", "xenvg/qa-test"]),
350     (False, _CLUSTER_VERIFY),
351     ]:
352     AssertCommand(cmd, fail=fail)
353
354
355 def TestClusterModifyEmpty():
356   """gnt-cluster modify"""
357   AssertCommand(["gnt-cluster", "modify"], fail=True)
358
359
360 def TestClusterModifyDisk():
361   """gnt-cluster modify -D"""
362   for param in _FAIL_PARAMS:
363     AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
364
365
366 def TestClusterModifyBe():
367   """gnt-cluster modify -B"""
368   for fail, cmd in [
369     # max/min mem
370     (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
371     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
372     (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
373     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
374     (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
375     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
376     (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
377     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
378     (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
379     (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
380     (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
381     # vcpus
382     (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
383     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
384     (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
385     (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
386     (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
387     # auto_balance
388     (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
389     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
390     (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
391     (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
392     (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
393     ]:
394     AssertCommand(cmd, fail=fail)
395
396   # redo the original-requested BE parameters, if any
397   bep = qa_config.get("backend-parameters", "")
398   if bep:
399     AssertCommand(["gnt-cluster", "modify", "-B", bep])
400
401
402 def TestClusterInfo():
403   """gnt-cluster info"""
404   AssertCommand(["gnt-cluster", "info"])
405
406
407 def TestClusterRedistConf():
408   """gnt-cluster redist-conf"""
409   AssertCommand(["gnt-cluster", "redist-conf"])
410
411
412 def TestClusterGetmaster():
413   """gnt-cluster getmaster"""
414   AssertCommand(["gnt-cluster", "getmaster"])
415
416
417 def TestClusterVersion():
418   """gnt-cluster version"""
419   AssertCommand(["gnt-cluster", "version"])
420
421
422 def TestClusterRenewCrypto():
423   """gnt-cluster renew-crypto"""
424   master = qa_config.GetMasterNode()
425
426   # Conflicting options
427   cmd = ["gnt-cluster", "renew-crypto", "--force",
428          "--new-cluster-certificate", "--new-confd-hmac-key"]
429   conflicting = [
430     ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
431     ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
432     ]
433   for i in conflicting:
434     AssertCommand(cmd + i, fail=True)
435
436   # Invalid RAPI certificate
437   cmd = ["gnt-cluster", "renew-crypto", "--force",
438          "--rapi-certificate=/dev/null"]
439   AssertCommand(cmd, fail=True)
440
441   rapi_cert_backup = qa_utils.BackupFile(master["primary"],
442                                          pathutils.RAPI_CERT_FILE)
443   try:
444     # Custom RAPI certificate
445     fh = tempfile.NamedTemporaryFile()
446
447     # Ensure certificate doesn't cause "gnt-cluster verify" to complain
448     validity = constants.SSL_CERT_EXPIRATION_WARN * 3
449
450     utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
451
452     tmpcert = qa_utils.UploadFile(master["primary"], fh.name)
453     try:
454       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
455                      "--rapi-certificate=%s" % tmpcert])
456     finally:
457       AssertCommand(["rm", "-f", tmpcert])
458
459     # Custom cluster domain secret
460     cds_fh = tempfile.NamedTemporaryFile()
461     cds_fh.write(utils.GenerateSecret())
462     cds_fh.write("\n")
463     cds_fh.flush()
464
465     tmpcds = qa_utils.UploadFile(master["primary"], cds_fh.name)
466     try:
467       AssertCommand(["gnt-cluster", "renew-crypto", "--force",
468                      "--cluster-domain-secret=%s" % tmpcds])
469     finally:
470       AssertCommand(["rm", "-f", tmpcds])
471
472     # Normal case
473     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
474                    "--new-cluster-certificate", "--new-confd-hmac-key",
475                    "--new-rapi-certificate", "--new-cluster-domain-secret"])
476
477     # Restore RAPI certificate
478     AssertCommand(["gnt-cluster", "renew-crypto", "--force",
479                    "--rapi-certificate=%s" % rapi_cert_backup])
480   finally:
481     AssertCommand(["rm", "-f", rapi_cert_backup])
482
483
484 def TestClusterBurnin():
485   """Burnin"""
486   master = qa_config.GetMasterNode()
487
488   options = qa_config.get("options", {})
489   disk_template = options.get("burnin-disk-template", "drbd")
490   parallel = options.get("burnin-in-parallel", False)
491   check_inst = options.get("burnin-check-instances", False)
492   do_rename = options.get("burnin-rename", "")
493   do_reboot = options.get("burnin-reboot", True)
494   reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
495
496   # Get as many instances as we need
497   instances = []
498   try:
499     try:
500       num = qa_config.get("options", {}).get("burnin-instances", 1)
501       for _ in range(0, num):
502         instances.append(qa_config.AcquireInstance())
503     except qa_error.OutOfInstancesError:
504       print "Not enough instances, continuing anyway."
505
506     if len(instances) < 1:
507       raise qa_error.Error("Burnin needs at least one instance")
508
509     script = qa_utils.UploadFile(master["primary"], "../tools/burnin")
510     try:
511       # Run burnin
512       cmd = [script,
513              "--os=%s" % qa_config.get("os"),
514              "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
515              "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
516              "--disk-size=%s" % ",".join(qa_config.get("disk")),
517              "--disk-growth=%s" % ",".join(qa_config.get("disk-growth")),
518              "--disk-template=%s" % disk_template]
519       if parallel:
520         cmd.append("--parallel")
521         cmd.append("--early-release")
522       if check_inst:
523         cmd.append("--http-check")
524       if do_rename:
525         cmd.append("--rename=%s" % do_rename)
526       if not do_reboot:
527         cmd.append("--no-reboot")
528       else:
529         cmd.append("--reboot-types=%s" % ",".join(reboot_types))
530       cmd += [inst["name"] for inst in instances]
531       AssertCommand(cmd)
532     finally:
533       AssertCommand(["rm", "-f", script])
534
535   finally:
536     for inst in instances:
537       qa_config.ReleaseInstance(inst)
538
539
540 def TestClusterMasterFailover():
541   """gnt-cluster master-failover"""
542   master = qa_config.GetMasterNode()
543   failovermaster = qa_config.AcquireNode(exclude=master)
544
545   cmd = ["gnt-cluster", "master-failover"]
546   try:
547     AssertCommand(cmd, node=failovermaster)
548     # Back to original master node
549     AssertCommand(cmd, node=master)
550   finally:
551     qa_config.ReleaseNode(failovermaster)
552
553
554 def TestClusterMasterFailoverWithDrainedQueue():
555   """gnt-cluster master-failover with drained queue"""
556   drain_check = ["test", "-f", pathutils.JOB_QUEUE_DRAIN_FILE]
557
558   master = qa_config.GetMasterNode()
559   failovermaster = qa_config.AcquireNode(exclude=master)
560
561   # Ensure queue is not drained
562   for node in [master, failovermaster]:
563     AssertCommand(drain_check, node=node, fail=True)
564
565   # Drain queue on failover master
566   AssertCommand(["touch", pathutils.JOB_QUEUE_DRAIN_FILE], node=failovermaster)
567
568   cmd = ["gnt-cluster", "master-failover"]
569   try:
570     AssertCommand(drain_check, node=failovermaster)
571     AssertCommand(cmd, node=failovermaster)
572     AssertCommand(drain_check, fail=True)
573     AssertCommand(drain_check, node=failovermaster, fail=True)
574
575     # Back to original master node
576     AssertCommand(cmd, node=master)
577   finally:
578     qa_config.ReleaseNode(failovermaster)
579
580   AssertCommand(drain_check, fail=True)
581   AssertCommand(drain_check, node=failovermaster, fail=True)
582
583
584 def TestClusterCopyfile():
585   """gnt-cluster copyfile"""
586   master = qa_config.GetMasterNode()
587
588   uniqueid = utils.NewUUID()
589
590   # Create temporary file
591   f = tempfile.NamedTemporaryFile()
592   f.write(uniqueid)
593   f.flush()
594   f.seek(0)
595
596   # Upload file to master node
597   testname = qa_utils.UploadFile(master["primary"], f.name)
598   try:
599     # Copy file to all nodes
600     AssertCommand(["gnt-cluster", "copyfile", testname])
601     _CheckFileOnAllNodes(testname, uniqueid)
602   finally:
603     _RemoveFileFromAllNodes(testname)
604
605
606 def TestClusterCommand():
607   """gnt-cluster command"""
608   uniqueid = utils.NewUUID()
609   rfile = "/tmp/gnt%s" % utils.NewUUID()
610   rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
611   cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
612                               "%s >%s" % (rcmd, rfile)])
613
614   try:
615     AssertCommand(cmd)
616     _CheckFileOnAllNodes(rfile, uniqueid)
617   finally:
618     _RemoveFileFromAllNodes(rfile)
619
620
621 def TestClusterDestroy():
622   """gnt-cluster destroy"""
623   AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
624
625
626 def TestClusterRepairDiskSizes():
627   """gnt-cluster repair-disk-sizes"""
628   AssertCommand(["gnt-cluster", "repair-disk-sizes"])
629
630
631 def TestSetExclStorCluster(newvalue):
632   """Set the exclusive_storage node parameter at the cluster level.
633
634   @type newvalue: bool
635   @param newvalue: New value of exclusive_storage
636   @rtype: bool
637   @return: The old value of exclusive_storage
638
639   """
640   oldvalue = _GetBoolClusterField("exclusive_storage")
641   AssertCommand(["gnt-cluster", "modify", "--node-parameters",
642                  "exclusive_storage=%s" % newvalue])
643   effvalue = _GetBoolClusterField("exclusive_storage")
644   if effvalue != newvalue:
645     raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
646                          " of %s" % (effvalue, newvalue))
647   return oldvalue
648
649
650 def _BuildSetESCmd(value, node_name):
651   return ["gnt-node", "modify", "--node-parameters",
652           "exclusive_storage=%s" % value, node_name]
653
654
655 def TestExclStorSingleNode(node):
656   """cluster-verify reports exclusive_storage set only on one node.
657
658   """
659   node_name = node["primary"]
660   es_val = _GetBoolClusterField("exclusive_storage")
661   assert not es_val
662   AssertCommand(_BuildSetESCmd(True, node_name))
663   AssertClusterVerify(fail=True, errors=[constants.CV_EGROUPMIXEDESFLAG])
664   AssertCommand(_BuildSetESCmd("default", node_name))
665   AssertClusterVerify()