Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ b5a93c73

History | View | Annotate | Download (31.1 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
    # Get the ipolicy command
694
    mnode = qa_config.GetMasterNode()
695
    initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
696
    modcmd = ["gnt-cluster", "modify"]
697
    opts = initcmd.split()
698
    assert opts[0:2] == ["gnt-cluster", "init"]
699
    for k in range(2, len(opts) - 1):
700
      if opts[k].startswith("--ipolicy-"):
701
        assert k + 2 <= len(opts)
702
        modcmd.extend(opts[k:k + 2])
703
    # Re-apply the ipolicy (this should be a no-op)
704
    AssertCommand(modcmd)
705
    new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
706
    AssertEqual(initcmd, new_initcmd)
707

    
708

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

    
713

    
714
def TestClusterRedistConf():
715
  """gnt-cluster redist-conf"""
716
  AssertCommand(["gnt-cluster", "redist-conf"])
717

    
718

    
719
def TestClusterGetmaster():
720
  """gnt-cluster getmaster"""
721
  AssertCommand(["gnt-cluster", "getmaster"])
722

    
723

    
724
def TestClusterVersion():
725
  """gnt-cluster version"""
726
  AssertCommand(["gnt-cluster", "version"])
727

    
728

    
729
def TestClusterRenewCrypto():
730
  """gnt-cluster renew-crypto"""
731
  master = qa_config.GetMasterNode()
732

    
733
  # Conflicting options
734
  cmd = ["gnt-cluster", "renew-crypto", "--force",
735
         "--new-cluster-certificate", "--new-confd-hmac-key"]
736
  conflicting = [
737
    ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
738
    ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
739
    ]
740
  for i in conflicting:
741
    AssertCommand(cmd + i, fail=True)
742

    
743
  # Invalid RAPI certificate
744
  cmd = ["gnt-cluster", "renew-crypto", "--force",
745
         "--rapi-certificate=/dev/null"]
746
  AssertCommand(cmd, fail=True)
747

    
748
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
749
                                         pathutils.RAPI_CERT_FILE)
750
  try:
751
    # Custom RAPI certificate
752
    fh = tempfile.NamedTemporaryFile()
753

    
754
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
755
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
756

    
757
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
758

    
759
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
760
    try:
761
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
762
                     "--rapi-certificate=%s" % tmpcert])
763
    finally:
764
      AssertCommand(["rm", "-f", tmpcert])
765

    
766
    # Custom cluster domain secret
767
    cds_fh = tempfile.NamedTemporaryFile()
768
    cds_fh.write(utils.GenerateSecret())
769
    cds_fh.write("\n")
770
    cds_fh.flush()
771

    
772
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
773
    try:
774
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
775
                     "--cluster-domain-secret=%s" % tmpcds])
776
    finally:
777
      AssertCommand(["rm", "-f", tmpcds])
778

    
779
    # Normal case
780
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
781
                   "--new-cluster-certificate", "--new-confd-hmac-key",
782
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
783

    
784
    # Restore RAPI certificate
785
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
786
                   "--rapi-certificate=%s" % rapi_cert_backup])
787
  finally:
788
    AssertCommand(["rm", "-f", rapi_cert_backup])
789

    
790

    
791
def TestClusterBurnin():
792
  """Burnin"""
793
  master = qa_config.GetMasterNode()
794

    
795
  options = qa_config.get("options", {})
796
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
797
  parallel = options.get("burnin-in-parallel", False)
798
  check_inst = options.get("burnin-check-instances", False)
799
  do_rename = options.get("burnin-rename", "")
800
  do_reboot = options.get("burnin-reboot", True)
801
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
802

    
803
  # Get as many instances as we need
804
  instances = []
805
  try:
806
    try:
807
      num = qa_config.get("options", {}).get("burnin-instances", 1)
808
      for _ in range(0, num):
809
        instances.append(qa_config.AcquireInstance())
810
    except qa_error.OutOfInstancesError:
811
      print "Not enough instances, continuing anyway."
812

    
813
    if len(instances) < 1:
814
      raise qa_error.Error("Burnin needs at least one instance")
815

    
816
    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
817
    try:
818
      disks = qa_config.GetDiskOptions()
819
      # Run burnin
820
      cmd = [script,
821
             "--os=%s" % qa_config.get("os"),
822
             "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
823
             "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
824
             "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
825
             "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
826
             "--disk-template=%s" % disk_template]
827
      if parallel:
828
        cmd.append("--parallel")
829
        cmd.append("--early-release")
830
      if check_inst:
831
        cmd.append("--http-check")
832
      if do_rename:
833
        cmd.append("--rename=%s" % do_rename)
834
      if not do_reboot:
835
        cmd.append("--no-reboot")
836
      else:
837
        cmd.append("--reboot-types=%s" % ",".join(reboot_types))
838
      cmd += [inst.name for inst in instances]
839
      AssertCommand(cmd)
840
    finally:
841
      AssertCommand(["rm", "-f", script])
842

    
843
  finally:
844
    for inst in instances:
845
      inst.Release()
846

    
847

    
848
def TestClusterMasterFailover():
849
  """gnt-cluster master-failover"""
850
  master = qa_config.GetMasterNode()
851
  failovermaster = qa_config.AcquireNode(exclude=master)
852

    
853
  cmd = ["gnt-cluster", "master-failover"]
854
  try:
855
    AssertCommand(cmd, node=failovermaster)
856
    # Back to original master node
857
    AssertCommand(cmd, node=master)
858
  finally:
859
    failovermaster.Release()
860

    
861

    
862
def _NodeQueueDrainFile(node):
863
  """Returns path to queue drain file for a node.
864

865
  """
866
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
867

    
868

    
869
def _AssertDrainFile(node, **kwargs):
870
  """Checks for the queue drain file.
871

872
  """
873
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
874

    
875

    
876
def TestClusterMasterFailoverWithDrainedQueue():
877
  """gnt-cluster master-failover with drained queue"""
878
  master = qa_config.GetMasterNode()
879
  failovermaster = qa_config.AcquireNode(exclude=master)
880

    
881
  # Ensure queue is not drained
882
  for node in [master, failovermaster]:
883
    _AssertDrainFile(node, fail=True)
884

    
885
  # Drain queue on failover master
886
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
887
                node=failovermaster)
888

    
889
  cmd = ["gnt-cluster", "master-failover"]
890
  try:
891
    _AssertDrainFile(failovermaster)
892
    AssertCommand(cmd, node=failovermaster)
893
    _AssertDrainFile(master, fail=True)
894
    _AssertDrainFile(failovermaster, fail=True)
895

    
896
    # Back to original master node
897
    AssertCommand(cmd, node=master)
898
  finally:
899
    failovermaster.Release()
900

    
901
  # Ensure queue is not drained
902
  for node in [master, failovermaster]:
903
    _AssertDrainFile(node, fail=True)
904

    
905

    
906
def TestClusterCopyfile():
907
  """gnt-cluster copyfile"""
908
  master = qa_config.GetMasterNode()
909

    
910
  uniqueid = utils.NewUUID()
911

    
912
  # Create temporary file
913
  f = tempfile.NamedTemporaryFile()
914
  f.write(uniqueid)
915
  f.flush()
916
  f.seek(0)
917

    
918
  # Upload file to master node
919
  testname = qa_utils.UploadFile(master.primary, f.name)
920
  try:
921
    # Copy file to all nodes
922
    AssertCommand(["gnt-cluster", "copyfile", testname])
923
    _CheckFileOnAllNodes(testname, uniqueid)
924
  finally:
925
    _RemoveFileFromAllNodes(testname)
926

    
927

    
928
def TestClusterCommand():
929
  """gnt-cluster command"""
930
  uniqueid = utils.NewUUID()
931
  rfile = "/tmp/gnt%s" % utils.NewUUID()
932
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
933
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
934
                              "%s >%s" % (rcmd, rfile)])
935

    
936
  try:
937
    AssertCommand(cmd)
938
    _CheckFileOnAllNodes(rfile, uniqueid)
939
  finally:
940
    _RemoveFileFromAllNodes(rfile)
941

    
942

    
943
def TestClusterDestroy():
944
  """gnt-cluster destroy"""
945
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
946

    
947

    
948
def TestClusterRepairDiskSizes():
949
  """gnt-cluster repair-disk-sizes"""
950
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
951

    
952

    
953
def TestSetExclStorCluster(newvalue):
954
  """Set the exclusive_storage node parameter at the cluster level.
955

956
  @type newvalue: bool
957
  @param newvalue: New value of exclusive_storage
958
  @rtype: bool
959
  @return: The old value of exclusive_storage
960

961
  """
962
  es_path = ["Default node parameters", "exclusive_storage"]
963
  oldvalue = _GetClusterField(es_path)
964
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
965
                 "exclusive_storage=%s" % newvalue])
966
  effvalue = _GetClusterField(es_path)
967
  if effvalue != newvalue:
968
    raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
969
                         " of %s" % (effvalue, newvalue))
970
  qa_config.SetExclusiveStorage(newvalue)
971
  return oldvalue
972

    
973

    
974
def TestExclStorSharedPv(node):
975
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
976

977
  """
978
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
979
  lvname1 = _QA_LV_PREFIX + "vol1"
980
  lvname2 = _QA_LV_PREFIX + "vol2"
981
  node_name = node.primary
982
  AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
983
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
984
  AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
985
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
986
                                         constants.CV_ENODEORPHANLV])
987
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
988
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
989
  AssertClusterVerify()