Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ 462f0faa

History | View | Annotate | Download (30.5 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
import qa_instance
39

    
40
from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41

    
42

    
43
# Prefix for LVM volumes created by QA code during tests
44
_QA_LV_PREFIX = "qa-"
45

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

    
49

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

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

    
57

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

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

    
66

    
67
def _GetClusterField(field_path):
68
  """Get the value of a cluster field.
69

70
  @type field_path: list of strings
71
  @param field_path: Names of the groups/fields to navigate to get the desired
72
      value, e.g. C{["Default node parameters", "oob_program"]}
73
  @return: The effective value of the field (the actual type depends on the
74
      chosen field)
75

76
  """
77
  assert isinstance(field_path, list)
78
  assert field_path
79
  ret = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
80
  for key in field_path:
81
    ret = ret[key]
82
  return ret
83

    
84

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

    
88

    
89
def _GetCVErrorCodes(cvout):
90
  errs = set()
91
  warns = set()
92
  for l in cvout.splitlines():
93
    m = _CVERROR_RE.match(l)
94
    if m:
95
      etype = m.group(1)
96
      ecode = m.group(2)
97
      if etype == "ERROR":
98
        errs.add(ecode)
99
      elif etype == "WARNING":
100
        warns.add(ecode)
101
  return (errs, warns)
102

    
103

    
104
def _CheckVerifyErrors(actual, expected, etype):
105
  exp_codes = compat.UniqueFrozenset(e for (_, e, _) in expected)
106
  if not actual.issuperset(exp_codes):
107
    missing = exp_codes.difference(actual)
108
    raise qa_error.Error("Cluster-verify didn't return these expected"
109
                         " %ss: %s" % (etype, utils.CommaJoin(missing)))
110

    
111

    
112
def AssertClusterVerify(fail=False, errors=None, warnings=None):
113
  """Run cluster-verify and check the result
114

115
  @type fail: bool
116
  @param fail: if cluster-verify is expected to fail instead of succeeding
117
  @type errors: list of tuples
118
  @param errors: List of CV_XXX errors that are expected; if specified, all the
119
      errors listed must appear in cluster-verify output. A non-empty value
120
      implies C{fail=True}.
121
  @type warnings: list of tuples
122
  @param warnings: Same as C{errors} but for warnings.
123

124
  """
125
  cvcmd = "gnt-cluster verify"
126
  mnode = qa_config.GetMasterNode()
127
  if errors or warnings:
128
    cvout = GetCommandOutput(mnode.primary, cvcmd + " --error-codes",
129
                             fail=(fail or errors))
130
    (act_errs, act_warns) = _GetCVErrorCodes(cvout)
131
    if errors:
132
      _CheckVerifyErrors(act_errs, errors, "error")
133
    if warnings:
134
      _CheckVerifyErrors(act_warns, warnings, "warning")
135
  else:
136
    AssertCommand(cvcmd, fail=fail, node=mnode)
137

    
138

    
139
# data for testing failures due to bad keys/values for disk parameters
140
_FAIL_PARAMS = ["nonexistent:resync-rate=1",
141
                "drbd:nonexistent=1",
142
                "drbd:resync-rate=invalid",
143
                ]
144

    
145

    
146
def TestClusterInitDisk():
147
  """gnt-cluster init -D"""
148
  name = qa_config.get("name")
149
  for param in _FAIL_PARAMS:
150
    AssertCommand(["gnt-cluster", "init", "-D", param, name], fail=True)
151

    
152

    
153
def TestClusterInit(rapi_user, rapi_secret):
154
  """gnt-cluster init"""
155
  master = qa_config.GetMasterNode()
156

    
157
  rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
158
  rapi_dir = os.path.dirname(rapi_users_path)
159

    
160
  # First create the RAPI credentials
161
  fh = tempfile.NamedTemporaryFile()
162
  try:
163
    fh.write("%s %s write\n" % (rapi_user, rapi_secret))
164
    fh.flush()
165

    
166
    tmpru = qa_utils.UploadFile(master.primary, fh.name)
167
    try:
168
      AssertCommand(["mkdir", "-p", rapi_dir])
169
      AssertCommand(["mv", tmpru, rapi_users_path])
170
    finally:
171
      AssertCommand(["rm", "-f", tmpru])
172
  finally:
173
    fh.close()
174

    
175
  # Initialize cluster
176
  cmd = [
177
    "gnt-cluster", "init",
178
    "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
179
    "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
180
    "--enabled-disk-templates=%s" %
181
      ",".join(qa_config.GetEnabledDiskTemplates())
182
    ]
183

    
184
  for spec_type in ("mem-size", "disk-size", "disk-count", "cpu-count",
185
                    "nic-count"):
186
    for spec_val in ("min", "max", "std"):
187
      spec = qa_config.get("ispec_%s_%s" %
188
                           (spec_type.replace("-", "_"), spec_val), None)
189
      if spec is not None:
190
        cmd.append("--specs-%s=%s=%d" % (spec_type, spec_val, spec))
191

    
192
  if master.secondary:
193
    cmd.append("--secondary-ip=%s" % master.secondary)
194

    
195
  vgname = qa_config.get("vg-name", None)
196
  if vgname:
197
    cmd.append("--vg-name=%s" % vgname)
198

    
199
  master_netdev = qa_config.get("master-netdev", None)
200
  if master_netdev:
201
    cmd.append("--master-netdev=%s" % master_netdev)
202

    
203
  nicparams = qa_config.get("default-nicparams", None)
204
  if nicparams:
205
    cmd.append("--nic-parameters=%s" %
206
               ",".join(utils.FormatKeyValue(nicparams)))
207

    
208
  # Cluster value of the exclusive-storage node parameter
209
  e_s = qa_config.get("exclusive-storage")
210
  if e_s is not None:
211
    cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
212
  else:
213
    e_s = False
214
  qa_config.SetExclusiveStorage(e_s)
215

    
216
  extra_args = qa_config.get("cluster-init-args")
217
  if extra_args:
218
    cmd.extend(extra_args)
219

    
220
  cmd.append(qa_config.get("name"))
221

    
222
  AssertCommand(cmd)
223

    
224
  cmd = ["gnt-cluster", "modify"]
225

    
226
  # hypervisor parameter modifications
227
  hvp = qa_config.get("hypervisor-parameters", {})
228
  for k, v in hvp.items():
229
    cmd.extend(["-H", "%s:%s" % (k, v)])
230
  # backend parameter modifications
231
  bep = qa_config.get("backend-parameters", "")
232
  if bep:
233
    cmd.extend(["-B", bep])
234

    
235
  if len(cmd) > 2:
236
    AssertCommand(cmd)
237

    
238
  # OS parameters
239
  osp = qa_config.get("os-parameters", {})
240
  for k, v in osp.items():
241
    AssertCommand(["gnt-os", "modify", "-O", v, k])
242

    
243
  # OS hypervisor parameters
244
  os_hvp = qa_config.get("os-hvp", {})
245
  for os_name in os_hvp:
246
    for hv, hvp in os_hvp[os_name].items():
247
      AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
248

    
249

    
250
def TestClusterRename():
251
  """gnt-cluster rename"""
252
  cmd = ["gnt-cluster", "rename", "-f"]
253

    
254
  original_name = qa_config.get("name")
255
  rename_target = qa_config.get("rename", None)
256
  if rename_target is None:
257
    print qa_utils.FormatError('"rename" entry is missing')
258
    return
259

    
260
  for data in [
261
    cmd + [rename_target],
262
    _CLUSTER_VERIFY,
263
    cmd + [original_name],
264
    _CLUSTER_VERIFY,
265
    ]:
266
    AssertCommand(data)
267

    
268

    
269
def TestClusterOob():
270
  """out-of-band framework"""
271
  oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
272

    
273
  AssertCommand(_CLUSTER_VERIFY)
274
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
275
                 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
276
                 utils.NewUUID()])
277

    
278
  AssertCommand(_CLUSTER_VERIFY, fail=True)
279

    
280
  AssertCommand(["touch", oob_path_exists])
281
  AssertCommand(["chmod", "0400", oob_path_exists])
282
  AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
283

    
284
  try:
285
    AssertCommand(["gnt-cluster", "modify", "--node-parameters",
286
                   "oob_program=%s" % oob_path_exists])
287

    
288
    AssertCommand(_CLUSTER_VERIFY, fail=True)
289

    
290
    AssertCommand(["chmod", "0500", oob_path_exists])
291
    AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
292

    
293
    AssertCommand(_CLUSTER_VERIFY)
294
  finally:
295
    AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
296

    
297
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
298
                 "oob_program="])
299

    
300

    
301
def TestClusterEpo():
302
  """gnt-cluster epo"""
303
  master = qa_config.GetMasterNode()
304

    
305
  # Assert that OOB is unavailable for all nodes
306
  result_output = GetCommandOutput(master.primary,
307
                                   "gnt-node list --verbose --no-headers -o"
308
                                   " powered")
309
  AssertEqual(compat.all(powered == "(unavail)"
310
                         for powered in result_output.splitlines()), True)
311

    
312
  # Conflicting
313
  AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
314
  # --all doesn't expect arguments
315
  AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
316

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

    
320
  # This shouldn't fail
321
  AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
322

    
323
  # All instances should have been stopped now
324
  result_output = GetCommandOutput(master.primary,
325
                                   "gnt-instance list --no-headers -o status")
326
  # ERROR_down because the instance is stopped but not recorded as such
327
  AssertEqual(compat.all(status == "ERROR_down"
328
                         for status in result_output.splitlines()), True)
329

    
330
  # Now start everything again
331
  AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
332

    
333
  # All instances should have been started now
334
  result_output = GetCommandOutput(master.primary,
335
                                   "gnt-instance list --no-headers -o status")
336
  AssertEqual(compat.all(status == "running"
337
                         for status in result_output.splitlines()), True)
338

    
339

    
340
def TestClusterVerify():
341
  """gnt-cluster verify"""
342
  AssertCommand(_CLUSTER_VERIFY)
343
  AssertCommand(["gnt-cluster", "verify-disks"])
344

    
345

    
346
def TestJobqueue():
347
  """gnt-debug test-jobqueue"""
348
  AssertCommand(["gnt-debug", "test-jobqueue"])
349

    
350

    
351
def TestDelay(node):
352
  """gnt-debug delay"""
353
  AssertCommand(["gnt-debug", "delay", "1"])
354
  AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
355
  AssertCommand(["gnt-debug", "delay", "--no-master",
356
                 "-n", node.primary, "1"])
357

    
358

    
359
def TestClusterReservedLvs():
360
  """gnt-cluster reserved lvs"""
361
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
362
  lvname = _QA_LV_PREFIX + "test"
363
  lvfullname = "/".join([vgname, lvname])
364
  for fail, cmd in [
365
    (False, _CLUSTER_VERIFY),
366
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
367
    (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
368
    (True, _CLUSTER_VERIFY),
369
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
370
             "%s,.*/other-test" % lvfullname]),
371
    (False, _CLUSTER_VERIFY),
372
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
373
             ".*/%s.*" % _QA_LV_PREFIX]),
374
    (False, _CLUSTER_VERIFY),
375
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
376
    (True, _CLUSTER_VERIFY),
377
    (False, ["lvremove", "-f", lvfullname]),
378
    (False, _CLUSTER_VERIFY),
379
    ]:
380
    AssertCommand(cmd, fail=fail)
381

    
382

    
383
def TestClusterModifyEmpty():
384
  """gnt-cluster modify"""
385
  AssertCommand(["gnt-cluster", "modify"], fail=True)
386

    
387

    
388
def TestClusterModifyDisk():
389
  """gnt-cluster modify -D"""
390
  for param in _FAIL_PARAMS:
391
    AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
392

    
393

    
394
def TestClusterModifyDiskTemplates():
395
  """gnt-cluster modify --enabled-disk-templates=..."""
396
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
397
  default_disk_template = qa_config.GetDefaultDiskTemplate()
398

    
399
  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
400
                                           enabled_disk_templates)
401

    
402
  _RestoreEnabledDiskTemplates()
403
  nodes = qa_config.AcquireManyNodes(2)
404

    
405
  instance_template = enabled_disk_templates[0]
406
  instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
407

    
408
  _TestClusterModifyUnusedDiskTemplate(instance_template)
409
  _TestClusterModifyUsedDiskTemplate(instance_template,
410
                                     enabled_disk_templates)
411

    
412
  qa_instance.TestInstanceRemove(instance)
413
  _RestoreEnabledDiskTemplates()
414

    
415

    
416
def _RestoreEnabledDiskTemplates():
417
  """Sets the list of enabled disk templates back to the list of enabled disk
418
     templates from the QA configuration. This can be used to make sure that
419
     the tests that modify the list of disk templates do not interfere with
420
     other tests.
421

422
  """
423
  AssertCommand(
424
    ["gnt-cluster", "modify",
425
     "--enabled-disk-template=%s" %
426
       ",".join(qa_config.GetEnabledDiskTemplates())],
427
    fail=False)
428

    
429

    
430
def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
431
                                             enabled_disk_templates):
432
  """Tests argument handling of 'gnt-cluster modify' with respect to
433
     the parameter '--enabled-disk-templates'. This test is independent
434
     of instances.
435

436
  """
437
  AssertCommand(
438
    ["gnt-cluster", "modify",
439
     "--enabled-disk-template=%s" %
440
       ",".join(enabled_disk_templates)],
441
    fail=False)
442

    
443
  # bogus templates
444
  AssertCommand(["gnt-cluster", "modify",
445
                 "--enabled-disk-templates=pinkbunny"],
446
                fail=True)
447

    
448
  # duplicate entries do no harm
449
  AssertCommand(
450
    ["gnt-cluster", "modify",
451
     "--enabled-disk-templates=%s,%s" %
452
      (default_disk_template, default_disk_template)],
453
    fail=False)
454

    
455

    
456
def _TestClusterModifyUsedDiskTemplate(instance_template,
457
                                       enabled_disk_templates):
458
  """Tests that disk templates that are currently in use by instances cannot
459
     be disabled on the cluster.
460

461
  """
462
  # If the list of enabled disk templates contains only one template
463
  # we need to add some other templates, because the list of enabled disk
464
  # templates can only be set to a non-empty list.
465
  new_disk_templates = list(set(enabled_disk_templates)
466
                              - set([instance_template]))
467
  if not new_disk_templates:
468
    new_disk_templates = list(set(constants.DISK_TEMPLATES)
469
                                - set([instance_template]))
470
  AssertCommand(
471
    ["gnt-cluster", "modify",
472
     "--enabled-disk-templates=%s" %
473
       ",".join(new_disk_templates)],
474
    fail=True)
475

    
476

    
477
def _TestClusterModifyUnusedDiskTemplate(instance_template):
478
  """Tests that unused disk templates can be disabled safely."""
479
  all_disk_templates = constants.DISK_TEMPLATES
480
  AssertCommand(
481
    ["gnt-cluster", "modify",
482
     "--enabled-disk-templates=%s" %
483
       ",".join(all_disk_templates)],
484
    fail=False)
485
  new_disk_templates = [instance_template]
486
  AssertCommand(
487
    ["gnt-cluster", "modify",
488
     "--enabled-disk-templates=%s" %
489
       ",".join(new_disk_templates)],
490
    fail=False)
491

    
492

    
493
def TestClusterModifyBe():
494
  """gnt-cluster modify -B"""
495
  for fail, cmd in [
496
    # max/min mem
497
    (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
498
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
499
    (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
500
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
501
    (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
502
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
503
    (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
504
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
505
    (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
506
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
507
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
508
    # vcpus
509
    (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
510
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
511
    (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
512
    (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
513
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
514
    # auto_balance
515
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
516
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
517
    (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
518
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
519
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
520
    ]:
521
    AssertCommand(cmd, fail=fail)
522

    
523
  # redo the original-requested BE parameters, if any
524
  bep = qa_config.get("backend-parameters", "")
525
  if bep:
526
    AssertCommand(["gnt-cluster", "modify", "-B", bep])
527

    
528

    
529
def _GetClusterIPolicy():
530
  """Return the run-time values of the cluster-level instance policy.
531

532
  @rtype: tuple
533
  @return: (policy, specs), where:
534
      - policy is a dictionary of the policy values, instance specs excluded
535
      - specs is dict of dict, specs[par][key] is a spec value, where key is
536
        "min", "max", or "std"
537

538
  """
539
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
540
  policy = info["Instance policy - limits for instances"]
541
  ret_specs = {}
542
  ret_policy = {}
543
  ispec_keys = constants.ISPECS_MINMAX_KEYS | frozenset([constants.ISPECS_STD])
544
  for (key, val) in policy.items():
545
    if key in ispec_keys:
546
      for (par, pval) in val.items():
547
        if par == "memory-size":
548
          par = "mem-size"
549
        d = ret_specs.setdefault(par, {})
550
        d[key] = pval
551
    else:
552
      ret_policy[key] = val
553

    
554
  # Sanity checks
555
  assert len(ret_specs) > 0
556
  good = all("min" in d and "std" in d and "max" in d
557
             for d in ret_specs.values())
558
  assert good, "Missing item in specs: %s" % ret_specs
559
  assert len(ret_policy) > 0
560
  return (ret_policy, ret_specs)
561

    
562

    
563
def TestClusterModifyIPolicy():
564
  """gnt-cluster modify --ipolicy-*"""
565
  basecmd = ["gnt-cluster", "modify"]
566
  (old_policy, old_specs) = _GetClusterIPolicy()
567
  for par in ["vcpu-ratio", "spindle-ratio"]:
568
    curr_val = float(old_policy[par])
569
    test_values = [
570
      (True, 1.0),
571
      (True, 1.5),
572
      (True, 2),
573
      (False, "a"),
574
      # Restore the old value
575
      (True, curr_val),
576
      ]
577
    for (good, val) in test_values:
578
      cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
579
      AssertCommand(cmd, fail=not good)
580
      if good:
581
        curr_val = val
582
      # Check the affected parameter
583
      (eff_policy, eff_specs) = _GetClusterIPolicy()
584
      AssertEqual(float(eff_policy[par]), curr_val)
585
      # Check everything else
586
      AssertEqual(eff_specs, old_specs)
587
      for p in eff_policy.keys():
588
        if p == par:
589
          continue
590
        AssertEqual(eff_policy[p], old_policy[p])
591

    
592
  # Disk templates are treated slightly differently
593
  par = "disk-templates"
594
  disp_str = "enabled disk templates"
595
  curr_val = old_policy[disp_str]
596
  test_values = [
597
    (True, constants.DT_PLAIN),
598
    (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
599
    (False, "thisisnotadisktemplate"),
600
    (False, ""),
601
    # Restore the old value
602
    (True, curr_val.replace(" ", "")),
603
    ]
604
  for (good, val) in test_values:
605
    cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
606
    AssertCommand(cmd, fail=not good)
607
    if good:
608
      curr_val = val
609
    # Check the affected parameter
610
    (eff_policy, eff_specs) = _GetClusterIPolicy()
611
    AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
612
    # Check everything else
613
    AssertEqual(eff_specs, old_specs)
614
    for p in eff_policy.keys():
615
      if p == disp_str:
616
        continue
617
      AssertEqual(eff_policy[p], old_policy[p])
618

    
619

    
620
def TestClusterSetISpecs(new_specs, fail=False, old_values=None):
621
  """Change instance specs.
622

623
  @type new_specs: dict of dict
624
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
625
      can be an empty dictionary.
626
  @type fail: bool
627
  @param fail: if the change is expected to fail
628
  @type old_values: tuple
629
  @param old_values: (old_policy, old_specs), as returned by
630
     L{_GetClusterIPolicy}
631
  @return: same as L{_GetClusterIPolicy}
632

633
  """
634
  if old_values:
635
    (old_policy, old_specs) = old_values
636
  else:
637
    (old_policy, old_specs) = _GetClusterIPolicy()
638
  if new_specs:
639
    cmd = ["gnt-cluster", "modify"]
640
    for (par, keyvals) in new_specs.items():
641
      if par == "spindle-use":
642
        # ignore spindle-use, which is not settable
643
        continue
644
      cmd += [
645
        "--specs-%s" % par,
646
        ",".join(["%s=%s" % (k, v) for (k, v) in keyvals.items()]),
647
        ]
648
    AssertCommand(cmd, fail=fail)
649
  # Check the new state
650
  (eff_policy, eff_specs) = _GetClusterIPolicy()
651
  AssertEqual(eff_policy, old_policy)
652
  if fail:
653
    AssertEqual(eff_specs, old_specs)
654
  else:
655
    for par in eff_specs:
656
      for key in eff_specs[par]:
657
        if par in new_specs and key in new_specs[par]:
658
          AssertEqual(int(eff_specs[par][key]), int(new_specs[par][key]))
659
        else:
660
          AssertEqual(int(eff_specs[par][key]), int(old_specs[par][key]))
661
  return (eff_policy, eff_specs)
662

    
663

    
664
def TestClusterModifyISpecs():
665
  """gnt-cluster modify --specs-*"""
666
  params = ["mem-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
667
  (cur_policy, cur_specs) = _GetClusterIPolicy()
668
  for par in params:
669
    test_values = [
670
      (True, 0, 4, 12),
671
      (True, 4, 4, 12),
672
      (True, 4, 12, 12),
673
      (True, 4, 4, 4),
674
      (False, 4, 0, 12),
675
      (False, 4, 16, 12),
676
      (False, 4, 4, 0),
677
      (False, 12, 4, 4),
678
      (False, 12, 4, 0),
679
      (False, "a", 4, 12),
680
      (False, 0, "a", 12),
681
      (False, 0, 4, "a"),
682
      # This is to restore the old values
683
      (True,
684
       cur_specs[par]["min"], cur_specs[par]["std"], cur_specs[par]["max"])
685
      ]
686
    for (good, mn, st, mx) in test_values:
687
      new_vals = {par: {"min": str(mn), "std": str(st), "max": str(mx)}}
688
      cur_state = (cur_policy, cur_specs)
689
      # We update cur_specs, as we've copied the values to restore already
690
      (cur_policy, cur_specs) = TestClusterSetISpecs(new_vals, fail=not good,
691
                                                     old_values=cur_state)
692

    
693

    
694
def TestClusterInfo():
695
  """gnt-cluster info"""
696
  AssertCommand(["gnt-cluster", "info"])
697

    
698

    
699
def TestClusterRedistConf():
700
  """gnt-cluster redist-conf"""
701
  AssertCommand(["gnt-cluster", "redist-conf"])
702

    
703

    
704
def TestClusterGetmaster():
705
  """gnt-cluster getmaster"""
706
  AssertCommand(["gnt-cluster", "getmaster"])
707

    
708

    
709
def TestClusterVersion():
710
  """gnt-cluster version"""
711
  AssertCommand(["gnt-cluster", "version"])
712

    
713

    
714
def TestClusterRenewCrypto():
715
  """gnt-cluster renew-crypto"""
716
  master = qa_config.GetMasterNode()
717

    
718
  # Conflicting options
719
  cmd = ["gnt-cluster", "renew-crypto", "--force",
720
         "--new-cluster-certificate", "--new-confd-hmac-key"]
721
  conflicting = [
722
    ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
723
    ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
724
    ]
725
  for i in conflicting:
726
    AssertCommand(cmd + i, fail=True)
727

    
728
  # Invalid RAPI certificate
729
  cmd = ["gnt-cluster", "renew-crypto", "--force",
730
         "--rapi-certificate=/dev/null"]
731
  AssertCommand(cmd, fail=True)
732

    
733
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
734
                                         pathutils.RAPI_CERT_FILE)
735
  try:
736
    # Custom RAPI certificate
737
    fh = tempfile.NamedTemporaryFile()
738

    
739
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
740
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
741

    
742
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
743

    
744
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
745
    try:
746
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
747
                     "--rapi-certificate=%s" % tmpcert])
748
    finally:
749
      AssertCommand(["rm", "-f", tmpcert])
750

    
751
    # Custom cluster domain secret
752
    cds_fh = tempfile.NamedTemporaryFile()
753
    cds_fh.write(utils.GenerateSecret())
754
    cds_fh.write("\n")
755
    cds_fh.flush()
756

    
757
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
758
    try:
759
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
760
                     "--cluster-domain-secret=%s" % tmpcds])
761
    finally:
762
      AssertCommand(["rm", "-f", tmpcds])
763

    
764
    # Normal case
765
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
766
                   "--new-cluster-certificate", "--new-confd-hmac-key",
767
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
768

    
769
    # Restore RAPI certificate
770
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
771
                   "--rapi-certificate=%s" % rapi_cert_backup])
772
  finally:
773
    AssertCommand(["rm", "-f", rapi_cert_backup])
774

    
775

    
776
def TestClusterBurnin():
777
  """Burnin"""
778
  master = qa_config.GetMasterNode()
779

    
780
  options = qa_config.get("options", {})
781
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
782
  parallel = options.get("burnin-in-parallel", False)
783
  check_inst = options.get("burnin-check-instances", False)
784
  do_rename = options.get("burnin-rename", "")
785
  do_reboot = options.get("burnin-reboot", True)
786
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
787

    
788
  # Get as many instances as we need
789
  instances = []
790
  try:
791
    try:
792
      num = qa_config.get("options", {}).get("burnin-instances", 1)
793
      for _ in range(0, num):
794
        instances.append(qa_config.AcquireInstance())
795
    except qa_error.OutOfInstancesError:
796
      print "Not enough instances, continuing anyway."
797

    
798
    if len(instances) < 1:
799
      raise qa_error.Error("Burnin needs at least one instance")
800

    
801
    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
802
    try:
803
      # Run burnin
804
      cmd = [script,
805
             "--os=%s" % qa_config.get("os"),
806
             "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
807
             "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
808
             "--disk-size=%s" % ",".join(qa_config.get("disk")),
809
             "--disk-growth=%s" % ",".join(qa_config.get("disk-growth")),
810
             "--disk-template=%s" % disk_template]
811
      if parallel:
812
        cmd.append("--parallel")
813
        cmd.append("--early-release")
814
      if check_inst:
815
        cmd.append("--http-check")
816
      if do_rename:
817
        cmd.append("--rename=%s" % do_rename)
818
      if not do_reboot:
819
        cmd.append("--no-reboot")
820
      else:
821
        cmd.append("--reboot-types=%s" % ",".join(reboot_types))
822
      cmd += [inst.name for inst in instances]
823
      AssertCommand(cmd)
824
    finally:
825
      AssertCommand(["rm", "-f", script])
826

    
827
  finally:
828
    for inst in instances:
829
      inst.Release()
830

    
831

    
832
def TestClusterMasterFailover():
833
  """gnt-cluster master-failover"""
834
  master = qa_config.GetMasterNode()
835
  failovermaster = qa_config.AcquireNode(exclude=master)
836

    
837
  cmd = ["gnt-cluster", "master-failover"]
838
  try:
839
    AssertCommand(cmd, node=failovermaster)
840
    # Back to original master node
841
    AssertCommand(cmd, node=master)
842
  finally:
843
    failovermaster.Release()
844

    
845

    
846
def _NodeQueueDrainFile(node):
847
  """Returns path to queue drain file for a node.
848

849
  """
850
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
851

    
852

    
853
def _AssertDrainFile(node, **kwargs):
854
  """Checks for the queue drain file.
855

856
  """
857
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
858

    
859

    
860
def TestClusterMasterFailoverWithDrainedQueue():
861
  """gnt-cluster master-failover with drained queue"""
862
  master = qa_config.GetMasterNode()
863
  failovermaster = qa_config.AcquireNode(exclude=master)
864

    
865
  # Ensure queue is not drained
866
  for node in [master, failovermaster]:
867
    _AssertDrainFile(node, fail=True)
868

    
869
  # Drain queue on failover master
870
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
871
                node=failovermaster)
872

    
873
  cmd = ["gnt-cluster", "master-failover"]
874
  try:
875
    _AssertDrainFile(failovermaster)
876
    AssertCommand(cmd, node=failovermaster)
877
    _AssertDrainFile(master, fail=True)
878
    _AssertDrainFile(failovermaster, fail=True)
879

    
880
    # Back to original master node
881
    AssertCommand(cmd, node=master)
882
  finally:
883
    failovermaster.Release()
884

    
885
  # Ensure queue is not drained
886
  for node in [master, failovermaster]:
887
    _AssertDrainFile(node, fail=True)
888

    
889

    
890
def TestClusterCopyfile():
891
  """gnt-cluster copyfile"""
892
  master = qa_config.GetMasterNode()
893

    
894
  uniqueid = utils.NewUUID()
895

    
896
  # Create temporary file
897
  f = tempfile.NamedTemporaryFile()
898
  f.write(uniqueid)
899
  f.flush()
900
  f.seek(0)
901

    
902
  # Upload file to master node
903
  testname = qa_utils.UploadFile(master.primary, f.name)
904
  try:
905
    # Copy file to all nodes
906
    AssertCommand(["gnt-cluster", "copyfile", testname])
907
    _CheckFileOnAllNodes(testname, uniqueid)
908
  finally:
909
    _RemoveFileFromAllNodes(testname)
910

    
911

    
912
def TestClusterCommand():
913
  """gnt-cluster command"""
914
  uniqueid = utils.NewUUID()
915
  rfile = "/tmp/gnt%s" % utils.NewUUID()
916
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
917
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
918
                              "%s >%s" % (rcmd, rfile)])
919

    
920
  try:
921
    AssertCommand(cmd)
922
    _CheckFileOnAllNodes(rfile, uniqueid)
923
  finally:
924
    _RemoveFileFromAllNodes(rfile)
925

    
926

    
927
def TestClusterDestroy():
928
  """gnt-cluster destroy"""
929
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
930

    
931

    
932
def TestClusterRepairDiskSizes():
933
  """gnt-cluster repair-disk-sizes"""
934
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
935

    
936

    
937
def TestSetExclStorCluster(newvalue):
938
  """Set the exclusive_storage node parameter at the cluster level.
939

940
  @type newvalue: bool
941
  @param newvalue: New value of exclusive_storage
942
  @rtype: bool
943
  @return: The old value of exclusive_storage
944

945
  """
946
  es_path = ["Default node parameters", "exclusive_storage"]
947
  oldvalue = _GetClusterField(es_path)
948
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
949
                 "exclusive_storage=%s" % newvalue])
950
  effvalue = _GetClusterField(es_path)
951
  if effvalue != newvalue:
952
    raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
953
                         " of %s" % (effvalue, newvalue))
954
  qa_config.SetExclusiveStorage(newvalue)
955
  return oldvalue
956

    
957

    
958
def TestExclStorSharedPv(node):
959
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
960

961
  """
962
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
963
  lvname1 = _QA_LV_PREFIX + "vol1"
964
  lvname2 = _QA_LV_PREFIX + "vol2"
965
  node_name = node.primary
966
  AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
967
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
968
  AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
969
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
970
                                         constants.CV_ENODEORPHANLV])
971
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
972
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
973
  AssertClusterVerify()