4 # Copyright (C) 2007, 2010, 2011, 2012 Google Inc.
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.
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.
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
22 """Cluster related QA tests.
29 from ganeti import constants
30 from ganeti import compat
31 from ganeti import utils
32 from ganeti import pathutils
38 from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41 #: cluster verify command
42 _CLUSTER_VERIFY = ["gnt-cluster", "verify"]
45 def _RemoveFileFromAllNodes(filename):
46 """Removes a file from all nodes.
49 for node in qa_config.get("nodes"):
50 AssertCommand(["rm", "-f", filename], node=node)
53 def _CheckFileOnAllNodes(filename, content):
54 """Verifies the content of the given file on all nodes.
57 cmd = utils.ShellQuoteArgs(["cat", filename])
58 for node in qa_config.get("nodes"):
59 AssertEqual(qa_utils.GetCommandOutput(node["primary"], cmd), content)
62 # data for testing failures due to bad keys/values for disk parameters
63 _FAIL_PARAMS = ["nonexistent:resync-rate=1",
65 "drbd:resync-rate=invalid",
69 def TestClusterInitDisk():
70 """gnt-cluster init -D"""
71 name = qa_config.get("name")
72 for param in _FAIL_PARAMS:
73 AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
76 def TestClusterInit(rapi_user, rapi_secret):
77 """gnt-cluster init"""
78 master = qa_config.GetMasterNode()
80 rapi_dir = os.path.dirname(pathutils.RAPI_USERS_FILE)
82 # First create the RAPI credentials
83 fh = tempfile.NamedTemporaryFile()
85 fh.write("%s %s write\n" % (rapi_user, rapi_secret))
88 tmpru = qa_utils.UploadFile(master["primary"], fh.name)
90 AssertCommand(["mkdir", "-p", rapi_dir])
91 AssertCommand(["mv", tmpru, pathutils.RAPI_USERS_FILE])
93 AssertCommand(["rm", "-f", tmpru])
99 "gnt-cluster", "init",
100 "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
101 "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
104 for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
106 for spec_val in ("min", "max", "std"):
107 spec = qa_config.get("ispec_%s_%s" %
108 (spec_type.replace("-", "_"), spec_val), None)
110 cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
112 if master.get("secondary", None):
113 cmd.append("--secondary-ip=%s" % master["secondary"])
115 master_netdev = qa_config.get("master-netdev", None)
117 cmd.append("--master-netdev=%s" % master_netdev)
119 nicparams = qa_config.get("default-nicparams", None)
121 cmd.append("--nic-parameters=%s" %
122 ",".join(utils.FormatKeyValue(nicparams)))
124 cmd.append(qa_config.get("name"))
127 cmd = ["gnt-cluster", "modify"]
129 # hypervisor parameter modifications
130 hvp = qa_config.get("hypervisor-parameters", {})
131 for k, v in hvp.items():
132 cmd.extend(["-H", "%s:%s" % (k, v)])
133 # backend parameter modifications
134 bep = qa_config.get("backend-parameters", "")
136 cmd.extend(["-B", bep])
142 osp = qa_config.get("os-parameters", {})
143 for k, v in osp.items():
144 AssertCommand(["gnt-os", "modify", "-O", v, k])
146 # OS hypervisor parameters
147 os_hvp = qa_config.get("os-hvp", {})
148 for os_name in os_hvp:
149 for hv, hvp in os_hvp[os_name].items():
150 AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
153 def TestClusterRename():
154 """gnt-cluster rename"""
155 cmd = ["gnt-cluster", "rename", "-f"]
157 original_name = qa_config.get("name")
158 rename_target = qa_config.get("rename", None)
159 if rename_target is None:
160 print qa_utils.FormatError('"rename" entry is missing')
164 cmd + [rename_target],
166 cmd + [original_name],
172 def TestClusterOob():
173 """out-of-band framework"""
174 oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
176 AssertCommand(_CLUSTER_VERIFY)
177 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
178 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
181 AssertCommand(_CLUSTER_VERIFY, fail=True)
183 AssertCommand(["touch", oob_path_exists])
184 AssertCommand(["chmod", "0400", oob_path_exists])
185 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
188 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
189 "oob_program=%s" % oob_path_exists])
191 AssertCommand(_CLUSTER_VERIFY, fail=True)
193 AssertCommand(["chmod", "0500", oob_path_exists])
194 AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
196 AssertCommand(_CLUSTER_VERIFY)
198 AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
200 AssertCommand(["gnt-cluster", "modify", "--node-parameters",
204 def TestClusterEpo():
205 """gnt-cluster epo"""
206 master = qa_config.GetMasterNode()
208 # Assert that OOB is unavailable for all nodes
209 result_output = GetCommandOutput(master["primary"],
210 "gnt-node list --verbose --no-headers -o"
212 AssertEqual(compat.all(powered == "(unavail)"
213 for powered in result_output.splitlines()), True)
216 AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
217 # --all doesn't expect arguments
218 AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
220 # Unless --all is given master is not allowed to be in the list
221 AssertCommand(["gnt-cluster", "epo", "-f", master["primary"]], fail=True)
223 # This shouldn't fail
224 AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
226 # All instances should have been stopped now
227 result_output = GetCommandOutput(master["primary"],
228 "gnt-instance list --no-headers -o status")
229 # ERROR_down because the instance is stopped but not recorded as such
230 AssertEqual(compat.all(status == "ERROR_down"
231 for status in result_output.splitlines()), True)
233 # Now start everything again
234 AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
236 # All instances should have been started now
237 result_output = GetCommandOutput(master["primary"],
238 "gnt-instance list --no-headers -o status")
239 AssertEqual(compat.all(status == "running"
240 for status in result_output.splitlines()), True)
243 def TestClusterVerify():
244 """gnt-cluster verify"""
245 AssertCommand(_CLUSTER_VERIFY)
246 AssertCommand(["gnt-cluster", "verify-disks"])
250 """gnt-debug test-jobqueue"""
251 AssertCommand(["gnt-debug", "test-jobqueue"])
255 """gnt-debug delay"""
256 AssertCommand(["gnt-debug", "delay", "1"])
257 AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
258 AssertCommand(["gnt-debug", "delay", "--no-master",
259 "-n", node["primary"], "1"])
262 def TestClusterReservedLvs():
263 """gnt-cluster reserved lvs"""
265 (False, _CLUSTER_VERIFY),
266 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
267 (False, ["lvcreate", "-L1G", "-nqa-test", "xenvg"]),
268 (True, _CLUSTER_VERIFY),
269 (False, ["gnt-cluster", "modify", "--reserved-lvs",
270 "xenvg/qa-test,.*/other-test"]),
271 (False, _CLUSTER_VERIFY),
272 (False, ["gnt-cluster", "modify", "--reserved-lvs", ".*/qa-.*"]),
273 (False, _CLUSTER_VERIFY),
274 (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
275 (True, _CLUSTER_VERIFY),
276 (False, ["lvremove", "-f", "xenvg/qa-test"]),
277 (False, _CLUSTER_VERIFY),
279 AssertCommand(cmd, fail=fail)
282 def TestClusterModifyEmpty():
283 """gnt-cluster modify"""
284 AssertCommand(["gnt-cluster", "modify"], fail=True)
287 def TestClusterModifyDisk():
288 """gnt-cluster modify -D"""
289 for param in _FAIL_PARAMS:
290 AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
293 def TestClusterModifyBe():
294 """gnt-cluster modify -B"""
297 (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
298 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
299 (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
300 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
301 (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
302 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
303 (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
304 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
305 (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
306 (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
307 (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
309 (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
310 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
311 (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
312 (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
313 (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
315 (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
316 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
317 (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
318 (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
319 (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
321 AssertCommand(cmd, fail=fail)
323 # redo the original-requested BE parameters, if any
324 bep = qa_config.get("backend-parameters", "")
326 AssertCommand(["gnt-cluster", "modify", "-B", bep])
329 def TestClusterInfo():
330 """gnt-cluster info"""
331 AssertCommand(["gnt-cluster", "info"])
334 def TestClusterRedistConf():
335 """gnt-cluster redist-conf"""
336 AssertCommand(["gnt-cluster", "redist-conf"])
339 def TestClusterGetmaster():
340 """gnt-cluster getmaster"""
341 AssertCommand(["gnt-cluster", "getmaster"])
344 def TestClusterVersion():
345 """gnt-cluster version"""
346 AssertCommand(["gnt-cluster", "version"])
349 def TestClusterRenewCrypto():
350 """gnt-cluster renew-crypto"""
351 master = qa_config.GetMasterNode()
353 # Conflicting options
354 cmd = ["gnt-cluster", "renew-crypto", "--force",
355 "--new-cluster-certificate", "--new-confd-hmac-key"]
357 ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
358 ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
360 for i in conflicting:
361 AssertCommand(cmd + i, fail=True)
363 # Invalid RAPI certificate
364 cmd = ["gnt-cluster", "renew-crypto", "--force",
365 "--rapi-certificate=/dev/null"]
366 AssertCommand(cmd, fail=True)
368 rapi_cert_backup = qa_utils.BackupFile(master["primary"],
369 pathutils.RAPI_CERT_FILE)
371 # Custom RAPI certificate
372 fh = tempfile.NamedTemporaryFile()
374 # Ensure certificate doesn't cause "gnt-cluster verify" to complain
375 validity = constants.SSL_CERT_EXPIRATION_WARN * 3
377 utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
379 tmpcert = qa_utils.UploadFile(master["primary"], fh.name)
381 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
382 "--rapi-certificate=%s" % tmpcert])
384 AssertCommand(["rm", "-f", tmpcert])
386 # Custom cluster domain secret
387 cds_fh = tempfile.NamedTemporaryFile()
388 cds_fh.write(utils.GenerateSecret())
392 tmpcds = qa_utils.UploadFile(master["primary"], cds_fh.name)
394 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
395 "--cluster-domain-secret=%s" % tmpcds])
397 AssertCommand(["rm", "-f", tmpcds])
400 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
401 "--new-cluster-certificate", "--new-confd-hmac-key",
402 "--new-rapi-certificate", "--new-cluster-domain-secret"])
404 # Restore RAPI certificate
405 AssertCommand(["gnt-cluster", "renew-crypto", "--force",
406 "--rapi-certificate=%s" % rapi_cert_backup])
408 AssertCommand(["rm", "-f", rapi_cert_backup])
411 def TestClusterBurnin():
413 master = qa_config.GetMasterNode()
415 options = qa_config.get("options", {})
416 disk_template = options.get("burnin-disk-template", "drbd")
417 parallel = options.get("burnin-in-parallel", False)
418 check_inst = options.get("burnin-check-instances", False)
419 do_rename = options.get("burnin-rename", "")
420 do_reboot = options.get("burnin-reboot", True)
421 reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
423 # Get as many instances as we need
427 num = qa_config.get("options", {}).get("burnin-instances", 1)
428 for _ in range(0, num):
429 instances.append(qa_config.AcquireInstance())
430 except qa_error.OutOfInstancesError:
431 print "Not enough instances, continuing anyway."
433 if len(instances) < 1:
434 raise qa_error.Error("Burnin needs at least one instance")
436 script = qa_utils.UploadFile(master["primary"], "../tools/burnin")
440 "--os=%s" % qa_config.get("os"),
441 "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
442 "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
443 "--disk-size=%s" % ",".join(qa_config.get("disk")),
444 "--disk-growth=%s" % ",".join(qa_config.get("disk-growth")),
445 "--disk-template=%s" % disk_template]
447 cmd.append("--parallel")
448 cmd.append("--early-release")
450 cmd.append("--http-check")
452 cmd.append("--rename=%s" % do_rename)
454 cmd.append("--no-reboot")
456 cmd.append("--reboot-types=%s" % ",".join(reboot_types))
457 cmd += [inst["name"] for inst in instances]
460 AssertCommand(["rm", "-f", script])
463 for inst in instances:
464 qa_config.ReleaseInstance(inst)
467 def TestClusterMasterFailover():
468 """gnt-cluster master-failover"""
469 master = qa_config.GetMasterNode()
470 failovermaster = qa_config.AcquireNode(exclude=master)
472 cmd = ["gnt-cluster", "master-failover"]
474 AssertCommand(cmd, node=failovermaster)
475 # Back to original master node
476 AssertCommand(cmd, node=master)
478 qa_config.ReleaseNode(failovermaster)
481 def TestClusterMasterFailoverWithDrainedQueue():
482 """gnt-cluster master-failover with drained queue"""
483 drain_check = ["test", "-f", pathutils.JOB_QUEUE_DRAIN_FILE]
485 master = qa_config.GetMasterNode()
486 failovermaster = qa_config.AcquireNode(exclude=master)
488 # Ensure queue is not drained
489 for node in [master, failovermaster]:
490 AssertCommand(drain_check, node=node, fail=True)
492 # Drain queue on failover master
493 AssertCommand(["touch", pathutils.JOB_QUEUE_DRAIN_FILE], node=failovermaster)
495 cmd = ["gnt-cluster", "master-failover"]
497 AssertCommand(drain_check, node=failovermaster)
498 AssertCommand(cmd, node=failovermaster)
499 AssertCommand(drain_check, fail=True)
500 AssertCommand(drain_check, node=failovermaster, fail=True)
502 # Back to original master node
503 AssertCommand(cmd, node=master)
505 qa_config.ReleaseNode(failovermaster)
507 AssertCommand(drain_check, fail=True)
508 AssertCommand(drain_check, node=failovermaster, fail=True)
511 def TestClusterCopyfile():
512 """gnt-cluster copyfile"""
513 master = qa_config.GetMasterNode()
515 uniqueid = utils.NewUUID()
517 # Create temporary file
518 f = tempfile.NamedTemporaryFile()
523 # Upload file to master node
524 testname = qa_utils.UploadFile(master["primary"], f.name)
526 # Copy file to all nodes
527 AssertCommand(["gnt-cluster", "copyfile", testname])
528 _CheckFileOnAllNodes(testname, uniqueid)
530 _RemoveFileFromAllNodes(testname)
533 def TestClusterCommand():
534 """gnt-cluster command"""
535 uniqueid = utils.NewUUID()
536 rfile = "/tmp/gnt%s" % utils.NewUUID()
537 rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
538 cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
539 "%s >%s" % (rcmd, rfile)])
543 _CheckFileOnAllNodes(rfile, uniqueid)
545 _RemoveFileFromAllNodes(rfile)
548 def TestClusterDestroy():
549 """gnt-cluster destroy"""
550 AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
553 def TestClusterRepairDiskSizes():
554 """gnt-cluster repair-disk-sizes"""
555 AssertCommand(["gnt-cluster", "repair-disk-sizes"])