Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ b7630577

History | View | Annotate | Download (30.8 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 a dictionary containing only the specs, using the internal
536
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
537

538
  """
539
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
540
  policy = info["Instance policy - limits for instances"]
541
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
542

    
543
  # Sanity checks
544
  assert "minmax" in ret_specs and "std" in ret_specs
545
  assert len(ret_specs["minmax"]) > 0
546
  assert len(ret_policy) > 0
547
  return (ret_policy, ret_specs)
548

    
549

    
550
def TestClusterModifyIPolicy():
551
  """gnt-cluster modify --ipolicy-*"""
552
  basecmd = ["gnt-cluster", "modify"]
553
  (old_policy, old_specs) = _GetClusterIPolicy()
554
  for par in ["vcpu-ratio", "spindle-ratio"]:
555
    curr_val = float(old_policy[par])
556
    test_values = [
557
      (True, 1.0),
558
      (True, 1.5),
559
      (True, 2),
560
      (False, "a"),
561
      # Restore the old value
562
      (True, curr_val),
563
      ]
564
    for (good, val) in test_values:
565
      cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
566
      AssertCommand(cmd, fail=not good)
567
      if good:
568
        curr_val = val
569
      # Check the affected parameter
570
      (eff_policy, eff_specs) = _GetClusterIPolicy()
571
      AssertEqual(float(eff_policy[par]), curr_val)
572
      # Check everything else
573
      AssertEqual(eff_specs, old_specs)
574
      for p in eff_policy.keys():
575
        if p == par:
576
          continue
577
        AssertEqual(eff_policy[p], old_policy[p])
578

    
579
  # Disk templates are treated slightly differently
580
  par = "disk-templates"
581
  disp_str = "allowed disk templates"
582
  curr_val = old_policy[disp_str]
583
  test_values = [
584
    (True, constants.DT_PLAIN),
585
    (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
586
    (False, "thisisnotadisktemplate"),
587
    (False, ""),
588
    # Restore the old value
589
    (True, curr_val.replace(" ", "")),
590
    ]
591
  for (good, val) in test_values:
592
    cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
593
    AssertCommand(cmd, fail=not good)
594
    if good:
595
      curr_val = val
596
    # Check the affected parameter
597
    (eff_policy, eff_specs) = _GetClusterIPolicy()
598
    AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
599
    # Check everything else
600
    AssertEqual(eff_specs, old_specs)
601
    for p in eff_policy.keys():
602
      if p == disp_str:
603
        continue
604
      AssertEqual(eff_policy[p], old_policy[p])
605

    
606

    
607
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
608
                         old_values=None):
609
  """Change instance specs.
610

611
  At most one of new_specs or diff_specs can be specified.
612

613
  @type new_specs: dict
614
  @param new_specs: new complete specs, in the same format returned by
615
      L{_GetClusterIPolicy}
616
  @type diff_specs: dict
617
  @param diff_specs: partial specs, it can be an incomplete specifications, but
618
      if min/max specs are specified, their number must match the number of the
619
      existing specs
620
  @type fail: bool
621
  @param fail: if the change is expected to fail
622
  @type old_values: tuple
623
  @param old_values: (old_policy, old_specs), as returned by
624
      L{_GetClusterIPolicy}
625
  @return: same as L{_GetClusterIPolicy}
626

627
  """
628
  build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
629
  return qa_utils.TestSetISpecs(
630
    new_specs=new_specs, diff_specs=diff_specs,
631
    get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
632
    fail=fail, old_values=old_values)
633

    
634

    
635
def TestClusterModifyISpecs():
636
  """gnt-cluster modify --specs-*"""
637
  params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
638
  (cur_policy, cur_specs) = _GetClusterIPolicy()
639
  # This test assumes that there is only one min/max bound
640
  assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
641
  for par in params:
642
    test_values = [
643
      (True, 0, 4, 12),
644
      (True, 4, 4, 12),
645
      (True, 4, 12, 12),
646
      (True, 4, 4, 4),
647
      (False, 4, 0, 12),
648
      (False, 4, 16, 12),
649
      (False, 4, 4, 0),
650
      (False, 12, 4, 4),
651
      (False, 12, 4, 0),
652
      (False, "a", 4, 12),
653
      (False, 0, "a", 12),
654
      (False, 0, 4, "a"),
655
      # This is to restore the old values
656
      (True,
657
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
658
       cur_specs[constants.ISPECS_STD][par],
659
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
660
      ]
661
    for (good, mn, st, mx) in test_values:
662
      new_vals = {
663
        constants.ISPECS_MINMAX: [{
664
          constants.ISPECS_MIN: {par: mn},
665
          constants.ISPECS_MAX: {par: mx}
666
          }],
667
        constants.ISPECS_STD: {par: st}
668
        }
669
      cur_state = (cur_policy, cur_specs)
670
      # We update cur_specs, as we've copied the values to restore already
671
      (cur_policy, cur_specs) = TestClusterSetISpecs(
672
        diff_specs=new_vals, fail=not good, old_values=cur_state)
673

    
674
    # Get the ipolicy command
675
    mnode = qa_config.GetMasterNode()
676
    initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
677
    modcmd = ["gnt-cluster", "modify"]
678
    opts = initcmd.split()
679
    assert opts[0:2] == ["gnt-cluster", "init"]
680
    for k in range(2, len(opts) - 1):
681
      if opts[k].startswith("--ipolicy-"):
682
        assert k + 2 <= len(opts)
683
        modcmd.extend(opts[k:k + 2])
684
    # Re-apply the ipolicy (this should be a no-op)
685
    AssertCommand(modcmd)
686
    new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
687
    AssertEqual(initcmd, new_initcmd)
688

    
689

    
690
def TestClusterInfo():
691
  """gnt-cluster info"""
692
  AssertCommand(["gnt-cluster", "info"])
693

    
694

    
695
def TestClusterRedistConf():
696
  """gnt-cluster redist-conf"""
697
  AssertCommand(["gnt-cluster", "redist-conf"])
698

    
699

    
700
def TestClusterGetmaster():
701
  """gnt-cluster getmaster"""
702
  AssertCommand(["gnt-cluster", "getmaster"])
703

    
704

    
705
def TestClusterVersion():
706
  """gnt-cluster version"""
707
  AssertCommand(["gnt-cluster", "version"])
708

    
709

    
710
def TestClusterRenewCrypto():
711
  """gnt-cluster renew-crypto"""
712
  master = qa_config.GetMasterNode()
713

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

    
724
  # Invalid RAPI certificate
725
  cmd = ["gnt-cluster", "renew-crypto", "--force",
726
         "--rapi-certificate=/dev/null"]
727
  AssertCommand(cmd, fail=True)
728

    
729
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
730
                                         pathutils.RAPI_CERT_FILE)
731
  try:
732
    # Custom RAPI certificate
733
    fh = tempfile.NamedTemporaryFile()
734

    
735
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
736
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
737

    
738
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
739

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

    
747
    # Custom cluster domain secret
748
    cds_fh = tempfile.NamedTemporaryFile()
749
    cds_fh.write(utils.GenerateSecret())
750
    cds_fh.write("\n")
751
    cds_fh.flush()
752

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

    
760
    # Normal case
761
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
762
                   "--new-cluster-certificate", "--new-confd-hmac-key",
763
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
764

    
765
    # Restore RAPI certificate
766
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
767
                   "--rapi-certificate=%s" % rapi_cert_backup])
768
  finally:
769
    AssertCommand(["rm", "-f", rapi_cert_backup])
770

    
771

    
772
def TestClusterBurnin():
773
  """Burnin"""
774
  master = qa_config.GetMasterNode()
775

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

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

    
794
    if len(instances) < 1:
795
      raise qa_error.Error("Burnin needs at least one instance")
796

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

    
824
  finally:
825
    for inst in instances:
826
      inst.Release()
827

    
828

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

    
834
  cmd = ["gnt-cluster", "master-failover"]
835
  node_list_cmd = ["gnt-node", "list"]
836
  try:
837
    AssertCommand(cmd, node=failovermaster)
838
    AssertCommand(node_list_cmd, node=failovermaster)
839
    # Back to original master node
840
    AssertCommand(cmd, node=master)
841
    AssertCommand(node_list_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()