Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ dacd8ba4

History | View | Annotate | Download (29.3 kB)

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
# Prefix for LVM volumes created by QA code during tests
43
_QA_LV_PREFIX = "qa-"
44

    
45
#: cluster verify command
46
_CLUSTER_VERIFY = ["gnt-cluster", "verify"]
47

    
48

    
49
def _RemoveFileFromAllNodes(filename):
50
  """Removes a file from all nodes.
51

52
  """
53
  for node in qa_config.get("nodes"):
54
    AssertCommand(["rm", "-f", filename], node=node)
55

    
56

    
57
def _CheckFileOnAllNodes(filename, content):
58
  """Verifies the content of the given file on all nodes.
59

60
  """
61
  cmd = utils.ShellQuoteArgs(["cat", filename])
62
  for node in qa_config.get("nodes"):
63
    AssertEqual(qa_utils.GetCommandOutput(node.primary, cmd), content)
64

    
65

    
66
# "gnt-cluster info" fields
67
_CIFIELD_RE = re.compile(r"^[-\s]*(?P<field>[^\s:]+):\s*(?P<value>\S.*)$")
68

    
69

    
70
def _GetBoolClusterField(field):
71
  """Get the Boolean value of a cluster field.
72

73
  This function currently assumes that the field name is unique in the cluster
74
  configuration. An assertion checks this assumption.
75

76
  @type field: string
77
  @param field: Name of the field
78
  @rtype: bool
79
  @return: The effective value of the field
80

81
  """
82
  master = qa_config.GetMasterNode()
83
  infocmd = "gnt-cluster info"
84
  info_out = qa_utils.GetCommandOutput(master.primary, infocmd)
85
  ret = None
86
  for l in info_out.splitlines():
87
    m = _CIFIELD_RE.match(l)
88
    # FIXME: There should be a way to specify a field through a hierarchy
89
    if m and m.group("field") == field:
90
      # Make sure that ignoring the hierarchy doesn't cause a double match
91
      assert ret is None
92
      ret = (m.group("value").lower() == "true")
93
  if ret is not None:
94
    return ret
95
  raise qa_error.Error("Field not found in cluster configuration: %s" % field)
96

    
97

    
98
# Cluster-verify errors (date, "ERROR", then error code)
99
_CVERROR_RE = re.compile(r"^[\w\s:]+\s+- (ERROR|WARNING):([A-Z0-9_-]+):")
100

    
101

    
102
def _GetCVErrorCodes(cvout):
103
  errs = set()
104
  warns = set()
105
  for l in cvout.splitlines():
106
    m = _CVERROR_RE.match(l)
107
    if m:
108
      etype = m.group(1)
109
      ecode = m.group(2)
110
      if etype == "ERROR":
111
        errs.add(ecode)
112
      elif etype == "WARNING":
113
        warns.add(ecode)
114
  return (errs, warns)
115

    
116

    
117
def _CheckVerifyErrors(actual, expected, etype):
118
  exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
119
  if not actual.issuperset(exp_codes):
120
    missing = exp_codes.difference(actual)
121
    raise qa_error.Error("Cluster-verify didn't return these expected"
122
                         " %ss: %s" % (etype, utils.CommaJoin(missing)))
123

    
124

    
125
def AssertClusterVerify(fail=False, errors=None, warnings=None):
126
  """Run cluster-verify and check the result
127

128
  @type fail: bool
129
  @param fail: if cluster-verify is expected to fail instead of succeeding
130
  @type errors: list of tuples
131
  @param errors: List of CV_XXX errors that are expected; if specified, all the
132
      errors listed must appear in cluster-verify output. A non-empty value
133
      implies C{fail=True}.
134
  @type warnings: list of tuples
135
  @param warnings: Same as C{errors} but for warnings.
136

137
  """
138
  cvcmd = "gnt-cluster verify"
139
  mnode = qa_config.GetMasterNode()
140
  if errors or warnings:
141
    cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
142
                             fail=(fail or errors))
143
    (act_errs, act_warns) = _GetCVErrorCodes(cvout)
144
    if errors:
145
      _CheckVerifyErrors(act_errs, errors, "error")
146
    if warnings:
147
      _CheckVerifyErrors(act_warns, warnings, "warning")
148
  else:
149
    AssertCommand(cvcmd, fail=fail, node=mnode)
150

    
151

    
152
# data for testing failures due to bad keys/values for disk parameters
153
_FAIL_PARAMS = ["nonexistent:resync-rate=1",
154
                "drbd:nonexistent=1",
155
                "drbd:resync-rate=invalid",
156
                ]
157

    
158

    
159
def TestClusterInitDisk():
160
  """gnt-cluster init -D"""
161
  name = qa_config.get("name")
162
  for param in _FAIL_PARAMS:
163
    AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
164

    
165

    
166
def TestClusterInit(rapi_user, rapi_secret):
167
  """gnt-cluster init"""
168
  master = qa_config.GetMasterNode()
169

    
170
  rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
171
  rapi_dir = os.path.dirname(rapi_users_path)
172

    
173
  # First create the RAPI credentials
174
  fh = tempfile.NamedTemporaryFile()
175
  try:
176
    fh.write("%s %s write\n" % (rapi_user, rapi_secret))
177
    fh.flush()
178

    
179
    tmpru = qa_utils.UploadFile(master.primary, fh.name)
180
    try:
181
      AssertCommand(["mkdir", "-p", rapi_dir])
182
      AssertCommand(["mv", tmpru, rapi_users_path])
183
    finally:
184
      AssertCommand(["rm", "-f", tmpru])
185
  finally:
186
    fh.close()
187

    
188
  # Initialize cluster
189
  cmd = [
190
    "gnt-cluster", "init",
191
    "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
192
    "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
193
    "--enabled-storage-types=%s" %
194
      ",".join(qa_config.GetEnabledStorageTypes())
195
    ]
196

    
197
  for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
198
                    "nic-count"):
199
    for spec_val in ("min", "max", "std"):
200
      spec = qa_config.get("ispec_%s_%s" %
201
                           (spec_type.replace("-", "_"), spec_val), None)
202
      if spec is not None:
203
        cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
204

    
205
  if master.secondary:
206
    cmd.append("--secondary-ip=%s" % master.secondary)
207

    
208
  vgname = qa_config.get("vg-name", None)
209
  if vgname:
210
    cmd.append("--vg-name=%s" % vgname)
211

    
212
  master_netdev = qa_config.get("master-netdev", None)
213
  if master_netdev:
214
    cmd.append("--master-netdev=%s" % master_netdev)
215

    
216
  nicparams = qa_config.get("default-nicparams", None)
217
  if nicparams:
218
    cmd.append("--nic-parameters=%s" %
219
               ",".join(utils.FormatKeyValue(nicparams)))
220

    
221
  # Cluster value of the exclusive-storage node parameter
222
  e_s = qa_config.get("exclusive-storage")
223
  if e_s is not None:
224
    cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
225
  else:
226
    e_s = False
227
  qa_config.SetExclusiveStorage(e_s)
228

    
229
  extra_args = qa_config.get("cluster-init-args")
230
  if extra_args:
231
    cmd.extend(extra_args)
232

    
233
  cmd.append(qa_config.get("name"))
234

    
235
  AssertCommand(cmd)
236

    
237
  cmd = ["gnt-cluster", "modify"]
238

    
239
  # hypervisor parameter modifications
240
  hvp = qa_config.get("hypervisor-parameters", {})
241
  for k, v in hvp.items():
242
    cmd.extend(["-H", "%s:%s" % (k, v)])
243
  # backend parameter modifications
244
  bep = qa_config.get("backend-parameters", "")
245
  if bep:
246
    cmd.extend(["-B", bep])
247

    
248
  if len(cmd) > 2:
249
    AssertCommand(cmd)
250

    
251
  # OS parameters
252
  osp = qa_config.get("os-parameters", {})
253
  for k, v in osp.items():
254
    AssertCommand(["gnt-os", "modify", "-O", v, k])
255

    
256
  # OS hypervisor parameters
257
  os_hvp = qa_config.get("os-hvp", {})
258
  for os_name in os_hvp:
259
    for hv, hvp in os_hvp[os_name].items():
260
      AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
261

    
262

    
263
def TestClusterRename():
264
  """gnt-cluster rename"""
265
  cmd = ["gnt-cluster", "rename", "-f"]
266

    
267
  original_name = qa_config.get("name")
268
  rename_target = qa_config.get("rename", None)
269
  if rename_target is None:
270
    print qa_utils.FormatError('"rename" entry is missing')
271
    return
272

    
273
  for data in [
274
    cmd + [rename_target],
275
    _CLUSTER_VERIFY,
276
    cmd + [original_name],
277
    _CLUSTER_VERIFY,
278
    ]:
279
    AssertCommand(data)
280

    
281

    
282
def TestClusterOob():
283
  """out-of-band framework"""
284
  oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
285

    
286
  AssertCommand(_CLUSTER_VERIFY)
287
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
288
                 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
289
                 utils.NewUUID()])
290

    
291
  AssertCommand(_CLUSTER_VERIFY, fail=True)
292

    
293
  AssertCommand(["touch", oob_path_exists])
294
  AssertCommand(["chmod", "0400", oob_path_exists])
295
  AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
296

    
297
  try:
298
    AssertCommand(["gnt-cluster", "modify", "--node-parameters",
299
                   "oob_program=%s" % oob_path_exists])
300

    
301
    AssertCommand(_CLUSTER_VERIFY, fail=True)
302

    
303
    AssertCommand(["chmod", "0500", oob_path_exists])
304
    AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
305

    
306
    AssertCommand(_CLUSTER_VERIFY)
307
  finally:
308
    AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
309

    
310
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
311
                 "oob_program="])
312

    
313

    
314
def TestClusterEpo():
315
  """gnt-cluster epo"""
316
  master = qa_config.GetMasterNode()
317

    
318
  # Assert that OOB is unavailable for all nodes
319
  result_output = GetCommandOutput(master.primary,
320
                                   "gnt-node list --verbose --no-headers -o"
321
                                   " powered")
322
  AssertEqual(compat.all(powered == "(unavail)"
323
                         for powered in result_output.splitlines()), True)
324

    
325
  # Conflicting
326
  AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
327
  # --all doesn't expect arguments
328
  AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
329

    
330
  # Unless --all is given master is not allowed to be in the list
331
  AssertCommand(["gnt-cluster", "epo", "-f", master.primary], fail=True)
332

    
333
  # This shouldn't fail
334
  AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
335

    
336
  # All instances should have been stopped now
337
  result_output = GetCommandOutput(master.primary,
338
                                   "gnt-instance list --no-headers -o status")
339
  # ERROR_down because the instance is stopped but not recorded as such
340
  AssertEqual(compat.all(status == "ERROR_down"
341
                         for status in result_output.splitlines()), True)
342

    
343
  # Now start everything again
344
  AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
345

    
346
  # All instances should have been started now
347
  result_output = GetCommandOutput(master.primary,
348
                                   "gnt-instance list --no-headers -o status")
349
  AssertEqual(compat.all(status == "running"
350
                         for status in result_output.splitlines()), True)
351

    
352

    
353
def TestClusterVerify():
354
  """gnt-cluster verify"""
355
  AssertCommand(_CLUSTER_VERIFY)
356
  AssertCommand(["gnt-cluster", "verify-disks"])
357

    
358

    
359
def TestJobqueue():
360
  """gnt-debug test-jobqueue"""
361
  AssertCommand(["gnt-debug", "test-jobqueue"])
362

    
363

    
364
def TestDelay(node):
365
  """gnt-debug delay"""
366
  AssertCommand(["gnt-debug", "delay", "1"])
367
  AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
368
  AssertCommand(["gnt-debug", "delay", "--no-master",
369
                 "-n", node.primary, "1"])
370

    
371

    
372
def TestClusterReservedLvs():
373
  """gnt-cluster reserved lvs"""
374
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
375
  lvname = _QA_LV_PREFIX + "test"
376
  lvfullname = "/".join([vgname, lvname])
377
  for fail, cmd in [
378
    (False, _CLUSTER_VERIFY),
379
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
380
    (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
381
    (True, _CLUSTER_VERIFY),
382
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
383
             "%s,.*/other-test" % lvfullname]),
384
    (False, _CLUSTER_VERIFY),
385
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
386
             ".*/%s.*" % _QA_LV_PREFIX]),
387
    (False, _CLUSTER_VERIFY),
388
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
389
    (True, _CLUSTER_VERIFY),
390
    (False, ["lvremove", "-f", lvfullname]),
391
    (False, _CLUSTER_VERIFY),
392
    ]:
393
    AssertCommand(cmd, fail=fail)
394

    
395

    
396
def TestClusterModifyEmpty():
397
  """gnt-cluster modify"""
398
  AssertCommand(["gnt-cluster", "modify"], fail=True)
399

    
400

    
401
def TestClusterModifyDisk():
402
  """gnt-cluster modify -D"""
403
  for param in _FAIL_PARAMS:
404
    AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
405

    
406

    
407
def TestClusterModifyStorageTypes():
408
  """gnt-cluster modify --enabled-storage-types=..."""
409
  default_storage_type = qa_config.GetDefaultStorageType()
410
  AssertCommand(
411
    ["gnt-cluster", "modify",
412
     "--enabled-storage-types=%s" % default_storage_type],
413
    fail=False)
414
  AssertCommand(["gnt-cluster", "info"])
415
  AssertCommand(
416
    ["gnt-cluster", "modify",
417
     "--enabled-storage-types=%s" %
418
       ",".join(qa_config.GetEnabledStorageTypes())],
419
    fail=False)
420
  AssertCommand(["gnt-cluster", "info"])
421
  # bogus types
422
  AssertCommand(["gnt-cluster", "modify",
423
                 "--enabled-storage-types=pinkbunny"],
424
                fail=True)
425
  # duplicate entries do no harm
426
  AssertCommand(
427
    ["gnt-cluster", "modify",
428
     "--enabled-storage-types=%s,%s" %
429
      (default_storage_type, default_storage_type)],
430
    fail=False)
431
  AssertCommand(["gnt-cluster", "info"])
432

    
433

    
434
def TestClusterModifyBe():
435
  """gnt-cluster modify -B"""
436
  for fail, cmd in [
437
    # max/min mem
438
    (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
439
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
440
    (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
441
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
442
    (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
443
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
444
    (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
445
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
446
    (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
447
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
448
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
449
    # vcpus
450
    (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
451
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
452
    (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
453
    (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
454
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
455
    # auto_balance
456
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
457
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
458
    (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
459
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
460
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
461
    ]:
462
    AssertCommand(cmd, fail=fail)
463

    
464
  # redo the original-requested BE parameters, if any
465
  bep = qa_config.get("backend-parameters", "")
466
  if bep:
467
    AssertCommand(["gnt-cluster", "modify", "-B", bep])
468

    
469

    
470
_START_IPOLICY_RE = re.compile(r"^(\s*)Instance policy")
471
_START_ISPEC_RE = re.compile(r"^\s+-\s+(std|min|max)")
472
_VALUE_RE = r"([^\s:][^:]*):\s+(\S.*)$"
473
_IPOLICY_PARAM_RE = re.compile(r"^\s+-\s+" + _VALUE_RE)
474
_ISPEC_VALUE_RE = re.compile(r"^\s+" + _VALUE_RE)
475

    
476

    
477
def _GetClusterIPolicy():
478
  """Return the run-time values of the cluster-level instance policy.
479

480
  @rtype: tuple
481
  @return: (policy, specs), where:
482
      - policy is a dictionary of the policy values, instance specs excluded
483
      - specs is dict of dict, specs[par][key] is a spec value, where key is
484
        "min", "max", or "std"
485

486
  """
487
  mnode = qa_config.GetMasterNode()
488
  info = GetCommandOutput(mnode.primary, "gnt-cluster info")
489
  inside_policy = False
490
  end_ispec_re = None
491
  curr_spec = ""
492
  specs = {}
493
  policy = {}
494
  for line in info.splitlines():
495
    if inside_policy:
496
      # The order of the matching is important, as some REs overlap
497
      m = _START_ISPEC_RE.match(line)
498
      if m:
499
        curr_spec = m.group(1)
500
        continue
501
      m = _IPOLICY_PARAM_RE.match(line)
502
      if m:
503
        policy[m.group(1)] = m.group(2).strip()
504
        continue
505
      m = _ISPEC_VALUE_RE.match(line)
506
      if m:
507
        assert curr_spec
508
        par = m.group(1)
509
        if par == "memory-size":
510
          par = "mem-size"
511
        d = specs.setdefault(par, {})
512
        d[curr_spec] = m.group(2).strip()
513
        continue
514
      assert end_ispec_re is not None
515
      if end_ispec_re.match(line):
516
        inside_policy = False
517
    else:
518
      m = _START_IPOLICY_RE.match(line)
519
      if m:
520
        inside_policy = True
521
        # We stop parsing when we find the same indentation level
522
        re_str = r"^\s{%s}\S" % len(m.group(1))
523
        end_ispec_re = re.compile(re_str)
524
  # Sanity checks
525
  assert len(specs) > 0
526
  good = ("min" in d and "std" in d and "max" in d for d in specs)
527
  assert good, "Missing item in specs: %s" % specs
528
  assert len(policy) > 0
529
  return (policy, specs)
530

    
531

    
532
def TestClusterModifyIPolicy():
533
  """gnt-cluster modify --ipolicy-*"""
534
  basecmd = ["gnt-cluster", "modify"]
535
  (old_policy, old_specs) = _GetClusterIPolicy()
536
  for par in ["vcpu-ratio", "spindle-ratio"]:
537
    curr_val = float(old_policy[par])
538
    test_values = [
539
      (True, 1.0),
540
      (True, 1.5),
541
      (True, 2),
542
      (False, "a"),
543
      # Restore the old value
544
      (True, curr_val),
545
      ]
546
    for (good, val) in test_values:
547
      cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
548
      AssertCommand(cmd, fail=not good)
549
      if good:
550
        curr_val = val
551
      # Check the affected parameter
552
      (eff_policy, eff_specs) = _GetClusterIPolicy()
553
      AssertEqual(float(eff_policy[par]), curr_val)
554
      # Check everything else
555
      AssertEqual(eff_specs, old_specs)
556
      for p in eff_policy.keys():
557
        if p == par:
558
          continue
559
        AssertEqual(eff_policy[p], old_policy[p])
560

    
561
  # Disk templates are treated slightly differently
562
  par = "disk-templates"
563
  disp_str = "enabled disk templates"
564
  curr_val = old_policy[disp_str]
565
  test_values = [
566
    (True, constants.DT_PLAIN),
567
    (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
568
    (False, "thisisnotadisktemplate"),
569
    (False, ""),
570
    # Restore the old value
571
    (True, curr_val.replace(" ", "")),
572
    ]
573
  for (good, val) in test_values:
574
    cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
575
    AssertCommand(cmd, fail=not good)
576
    if good:
577
      curr_val = val
578
    # Check the affected parameter
579
    (eff_policy, eff_specs) = _GetClusterIPolicy()
580
    AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
581
    # Check everything else
582
    AssertEqual(eff_specs, old_specs)
583
    for p in eff_policy.keys():
584
      if p == disp_str:
585
        continue
586
      AssertEqual(eff_policy[p], old_policy[p])
587

    
588

    
589
def TestClusterSetISpecs(new_specs, fail=False, old_values=None):
590
  """Change instance specs.
591

592
  @type new_specs: dict of dict
593
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
594
      can be an empty dictionary.
595
  @type fail: bool
596
  @param fail: if the change is expected to fail
597
  @type old_values: tuple
598
  @param old_values: (old_policy, old_specs), as returned by
599
     L{_GetClusterIPolicy}
600
  @return: same as L{_GetClusterIPolicy}
601

602
  """
603
  if old_values:
604
    (old_policy, old_specs) = old_values
605
  else:
606
    (old_policy, old_specs) = _GetClusterIPolicy()
607
  if new_specs:
608
    cmd = ["gnt-cluster", "modify"]
609
    for (par, keyvals) in new_specs.items():
610
      if par == "spindle-use":
611
        # ignore spindle-use, which is not settable
612
        continue
613
      cmd += [
614
        "--specs-%s" % par,
615
        ",".join(["%s=%s" % (k, v) for (k, v) in keyvals.items()]),
616
        ]
617
    AssertCommand(cmd, fail=fail)
618
  # Check the new state
619
  (eff_policy, eff_specs) = _GetClusterIPolicy()
620
  AssertEqual(eff_policy, old_policy)
621
  if fail:
622
    AssertEqual(eff_specs, old_specs)
623
  else:
624
    for par in eff_specs:
625
      for key in eff_specs[par]:
626
        if par in new_specs and key in new_specs[par]:
627
          AssertEqual(int(eff_specs[par][key]), int(new_specs[par][key]))
628
        else:
629
          AssertEqual(int(eff_specs[par][key]), int(old_specs[par][key]))
630
  return (eff_policy, eff_specs)
631

    
632

    
633
def TestClusterModifyISpecs():
634
  """gnt-cluster modify --specs-*"""
635
  params = ["mem-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
636
  (cur_policy, cur_specs) = _GetClusterIPolicy()
637
  for par in params:
638
    test_values = [
639
      (True, 0, 4, 12),
640
      (True, 4, 4, 12),
641
      (True, 4, 12, 12),
642
      (True, 4, 4, 4),
643
      (False, 4, 0, 12),
644
      (False, 4, 16, 12),
645
      (False, 4, 4, 0),
646
      (False, 12, 4, 4),
647
      (False, 12, 4, 0),
648
      (False, "a", 4, 12),
649
      (False, 0, "a", 12),
650
      (False, 0, 4, "a"),
651
      # This is to restore the old values
652
      (True,
653
       cur_specs[par]["min"], cur_specs[par]["std"], cur_specs[par]["max"])
654
      ]
655
    for (good, mn, st, mx) in test_values:
656
      new_vals = {par: {"min": str(mn), "std": str(st), "max": str(mx)}}
657
      cur_state = (cur_policy, cur_specs)
658
      # We update cur_specs, as we've copied the values to restore already
659
      (cur_policy, cur_specs) = TestClusterSetISpecs(new_vals, fail=not good,
660
                                                     old_values=cur_state)
661

    
662

    
663
def TestClusterInfo():
664
  """gnt-cluster info"""
665
  AssertCommand(["gnt-cluster", "info"])
666

    
667

    
668
def TestClusterRedistConf():
669
  """gnt-cluster redist-conf"""
670
  AssertCommand(["gnt-cluster", "redist-conf"])
671

    
672

    
673
def TestClusterGetmaster():
674
  """gnt-cluster getmaster"""
675
  AssertCommand(["gnt-cluster", "getmaster"])
676

    
677

    
678
def TestClusterVersion():
679
  """gnt-cluster version"""
680
  AssertCommand(["gnt-cluster", "version"])
681

    
682

    
683
def TestClusterRenewCrypto():
684
  """gnt-cluster renew-crypto"""
685
  master = qa_config.GetMasterNode()
686

    
687
  # Conflicting options
688
  cmd = ["gnt-cluster", "renew-crypto", "--force",
689
         "--new-cluster-certificate", "--new-confd-hmac-key"]
690
  conflicting = [
691
    ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
692
    ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
693
    ]
694
  for i in conflicting:
695
    AssertCommand(cmd + i, fail=True)
696

    
697
  # Invalid RAPI certificate
698
  cmd = ["gnt-cluster", "renew-crypto", "--force",
699
         "--rapi-certificate=/dev/null"]
700
  AssertCommand(cmd, fail=True)
701

    
702
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
703
                                         pathutils.RAPI_CERT_FILE)
704
  try:
705
    # Custom RAPI certificate
706
    fh = tempfile.NamedTemporaryFile()
707

    
708
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
709
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
710

    
711
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
712

    
713
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
714
    try:
715
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
716
                     "--rapi-certificate=%s" % tmpcert])
717
    finally:
718
      AssertCommand(["rm", "-f", tmpcert])
719

    
720
    # Custom cluster domain secret
721
    cds_fh = tempfile.NamedTemporaryFile()
722
    cds_fh.write(utils.GenerateSecret())
723
    cds_fh.write("\n")
724
    cds_fh.flush()
725

    
726
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
727
    try:
728
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
729
                     "--cluster-domain-secret=%s" % tmpcds])
730
    finally:
731
      AssertCommand(["rm", "-f", tmpcds])
732

    
733
    # Normal case
734
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
735
                   "--new-cluster-certificate", "--new-confd-hmac-key",
736
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
737

    
738
    # Restore RAPI certificate
739
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
740
                   "--rapi-certificate=%s" % rapi_cert_backup])
741
  finally:
742
    AssertCommand(["rm", "-f", rapi_cert_backup])
743

    
744

    
745
def TestClusterBurnin():
746
  """Burnin"""
747
  master = qa_config.GetMasterNode()
748

    
749
  options = qa_config.get("options", {})
750
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
751
  parallel = options.get("burnin-in-parallel", False)
752
  check_inst = options.get("burnin-check-instances", False)
753
  do_rename = options.get("burnin-rename", "")
754
  do_reboot = options.get("burnin-reboot", True)
755
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
756

    
757
  # Get as many instances as we need
758
  instances = []
759
  try:
760
    try:
761
      num = qa_config.get("options", {}).get("burnin-instances", 1)
762
      for _ in range(0, num):
763
        instances.append(qa_config.AcquireInstance())
764
    except qa_error.OutOfInstancesError:
765
      print "Not enough instances, continuing anyway."
766

    
767
    if len(instances) < 1:
768
      raise qa_error.Error("Burnin needs at least one instance")
769

    
770
    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
771
    try:
772
      # Run burnin
773
      cmd = [script,
774
             "--os=%s" % qa_config.get("os"),
775
             "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
776
             "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
777
             "--disk-size=%s" % ",".join(qa_config.get("disk")),
778
             "--disk-growth=%s" % ",".join(qa_config.get("disk-growth")),
779
             "--disk-template=%s" % disk_template]
780
      if parallel:
781
        cmd.append("--parallel")
782
        cmd.append("--early-release")
783
      if check_inst:
784
        cmd.append("--http-check")
785
      if do_rename:
786
        cmd.append("--rename=%s" % do_rename)
787
      if not do_reboot:
788
        cmd.append("--no-reboot")
789
      else:
790
        cmd.append("--reboot-types=%s" % ",".join(reboot_types))
791
      cmd += [inst.name for inst in instances]
792
      AssertCommand(cmd)
793
    finally:
794
      AssertCommand(["rm", "-f", script])
795

    
796
  finally:
797
    for inst in instances:
798
      inst.Release()
799

    
800

    
801
def TestClusterMasterFailover():
802
  """gnt-cluster master-failover"""
803
  master = qa_config.GetMasterNode()
804
  failovermaster = qa_config.AcquireNode(exclude=master)
805

    
806
  cmd = ["gnt-cluster", "master-failover"]
807
  try:
808
    AssertCommand(cmd, node=failovermaster)
809
    # Back to original master node
810
    AssertCommand(cmd, node=master)
811
  finally:
812
    failovermaster.Release()
813

    
814

    
815
def _NodeQueueDrainFile(node):
816
  """Returns path to queue drain file for a node.
817

818
  """
819
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
820

    
821

    
822
def _AssertDrainFile(node, **kwargs):
823
  """Checks for the queue drain file.
824

825
  """
826
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
827

    
828

    
829
def TestClusterMasterFailoverWithDrainedQueue():
830
  """gnt-cluster master-failover with drained queue"""
831
  master = qa_config.GetMasterNode()
832
  failovermaster = qa_config.AcquireNode(exclude=master)
833

    
834
  # Ensure queue is not drained
835
  for node in [master, failovermaster]:
836
    _AssertDrainFile(node, fail=True)
837

    
838
  # Drain queue on failover master
839
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
840
                node=failovermaster)
841

    
842
  cmd = ["gnt-cluster", "master-failover"]
843
  try:
844
    _AssertDrainFile(failovermaster)
845
    AssertCommand(cmd, node=failovermaster)
846
    _AssertDrainFile(master, fail=True)
847
    _AssertDrainFile(failovermaster, fail=True)
848

    
849
    # Back to original master node
850
    AssertCommand(cmd, node=master)
851
  finally:
852
    failovermaster.Release()
853

    
854
  # Ensure queue is not drained
855
  for node in [master, failovermaster]:
856
    _AssertDrainFile(node, fail=True)
857

    
858

    
859
def TestClusterCopyfile():
860
  """gnt-cluster copyfile"""
861
  master = qa_config.GetMasterNode()
862

    
863
  uniqueid = utils.NewUUID()
864

    
865
  # Create temporary file
866
  f = tempfile.NamedTemporaryFile()
867
  f.write(uniqueid)
868
  f.flush()
869
  f.seek(0)
870

    
871
  # Upload file to master node
872
  testname = qa_utils.UploadFile(master.primary, f.name)
873
  try:
874
    # Copy file to all nodes
875
    AssertCommand(["gnt-cluster", "copyfile", testname])
876
    _CheckFileOnAllNodes(testname, uniqueid)
877
  finally:
878
    _RemoveFileFromAllNodes(testname)
879

    
880

    
881
def TestClusterCommand():
882
  """gnt-cluster command"""
883
  uniqueid = utils.NewUUID()
884
  rfile = "/tmp/gnt%s" % utils.NewUUID()
885
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
886
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
887
                              "%s >%s" % (rcmd, rfile)])
888

    
889
  try:
890
    AssertCommand(cmd)
891
    _CheckFileOnAllNodes(rfile, uniqueid)
892
  finally:
893
    _RemoveFileFromAllNodes(rfile)
894

    
895

    
896
def TestClusterDestroy():
897
  """gnt-cluster destroy"""
898
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
899

    
900

    
901
def TestClusterRepairDiskSizes():
902
  """gnt-cluster repair-disk-sizes"""
903
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
904

    
905

    
906
def TestSetExclStorCluster(newvalue):
907
  """Set the exclusive_storage node parameter at the cluster level.
908

909
  @type newvalue: bool
910
  @param newvalue: New value of exclusive_storage
911
  @rtype: bool
912
  @return: The old value of exclusive_storage
913

914
  """
915
  oldvalue = _GetBoolClusterField("exclusive_storage")
916
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
917
                 "exclusive_storage=%s" % newvalue])
918
  effvalue = _GetBoolClusterField("exclusive_storage")
919
  if effvalue != newvalue:
920
    raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
921
                         " of %s" % (effvalue, newvalue))
922
  qa_config.SetExclusiveStorage(newvalue)
923
  return oldvalue
924

    
925

    
926
def TestExclStorSharedPv(node):
927
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
928

929
  """
930
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
931
  lvname1 = _QA_LV_PREFIX + "vol1"
932
  lvname2 = _QA_LV_PREFIX + "vol2"
933
  node_name = node.primary
934
  AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
935
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
936
  AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
937
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
938
                                         constants.CV_ENODEORPHANLV])
939
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
940
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
941
  AssertClusterVerify()