Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ e4889779

History | View | Annotate | Download (36.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2010, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Cluster related QA tests.
23

24
"""
25

    
26
import re
27
import tempfile
28
import os.path
29

    
30
from ganeti import constants
31
from ganeti import compat
32
from ganeti import utils
33
from ganeti import pathutils
34

    
35
import qa_config
36
import qa_daemon
37
import qa_utils
38
import qa_error
39
import qa_instance
40

    
41
from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
42

    
43

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

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

    
50

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

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

    
58

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

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

    
67

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

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

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

    
85

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

    
89

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

    
104

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

    
112

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

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

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

    
139

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

    
146

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

    
153

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

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

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

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

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

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

    
195
  if master.secondary:
196
    cmd.append("--secondary-ip=%s" % master.secondary)
197

    
198
  if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
199
    vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
200
    if vgname:
201
      cmd.append("--vg-name=%s" % vgname)
202
    else:
203
      raise qa_error.Error("Please specify a volume group if you enable"
204
                           " lvm-based disk templates in the QA.")
205

    
206
  master_netdev = qa_config.get("master-netdev", None)
207
  if master_netdev:
208
    cmd.append("--master-netdev=%s" % master_netdev)
209

    
210
  nicparams = qa_config.get("default-nicparams", None)
211
  if nicparams:
212
    cmd.append("--nic-parameters=%s" %
213
               ",".join(utils.FormatKeyValue(nicparams)))
214

    
215
  # Cluster value of the exclusive-storage node parameter
216
  e_s = qa_config.get("exclusive-storage")
217
  if e_s is not None:
218
    cmd.extend(["--node-parameters", "exclusive_storage=%s" % e_s])
219
  else:
220
    e_s = False
221
  qa_config.SetExclusiveStorage(e_s)
222

    
223
  extra_args = qa_config.get("cluster-init-args")
224
  if extra_args:
225
    cmd.extend(extra_args)
226

    
227
  cmd.append(qa_config.get("name"))
228

    
229
  AssertCommand(cmd)
230

    
231
  cmd = ["gnt-cluster", "modify"]
232

    
233
  # hypervisor parameter modifications
234
  hvp = qa_config.get("hypervisor-parameters", {})
235
  for k, v in hvp.items():
236
    cmd.extend(["-H", "%s:%s" % (k, v)])
237
  # backend parameter modifications
238
  bep = qa_config.get("backend-parameters", "")
239
  if bep:
240
    cmd.extend(["-B", bep])
241

    
242
  if len(cmd) > 2:
243
    AssertCommand(cmd)
244

    
245
  # OS parameters
246
  osp = qa_config.get("os-parameters", {})
247
  for k, v in osp.items():
248
    AssertCommand(["gnt-os", "modify", "-O", v, k])
249

    
250
  # OS hypervisor parameters
251
  os_hvp = qa_config.get("os-hvp", {})
252
  for os_name in os_hvp:
253
    for hv, hvp in os_hvp[os_name].items():
254
      AssertCommand(["gnt-os", "modify", "-H", "%s:%s" % (hv, hvp), os_name])
255

    
256

    
257
def TestClusterRename():
258
  """gnt-cluster rename"""
259
  cmd = ["gnt-cluster", "rename", "-f"]
260

    
261
  original_name = qa_config.get("name")
262
  rename_target = qa_config.get("rename", None)
263
  if rename_target is None:
264
    print qa_utils.FormatError('"rename" entry is missing')
265
    return
266

    
267
  for data in [
268
    cmd + [rename_target],
269
    _CLUSTER_VERIFY,
270
    cmd + [original_name],
271
    _CLUSTER_VERIFY,
272
    ]:
273
    AssertCommand(data)
274

    
275

    
276
def TestClusterOob():
277
  """out-of-band framework"""
278
  oob_path_exists = "/tmp/ganeti-qa-oob-does-exist-%s" % utils.NewUUID()
279

    
280
  AssertCommand(_CLUSTER_VERIFY)
281
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
282
                 "oob_program=/tmp/ganeti-qa-oob-does-not-exist-%s" %
283
                 utils.NewUUID()])
284

    
285
  AssertCommand(_CLUSTER_VERIFY, fail=True)
286

    
287
  AssertCommand(["touch", oob_path_exists])
288
  AssertCommand(["chmod", "0400", oob_path_exists])
289
  AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
290

    
291
  try:
292
    AssertCommand(["gnt-cluster", "modify", "--node-parameters",
293
                   "oob_program=%s" % oob_path_exists])
294

    
295
    AssertCommand(_CLUSTER_VERIFY, fail=True)
296

    
297
    AssertCommand(["chmod", "0500", oob_path_exists])
298
    AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
299

    
300
    AssertCommand(_CLUSTER_VERIFY)
301
  finally:
302
    AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
303

    
304
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
305
                 "oob_program="])
306

    
307

    
308
def TestClusterEpo():
309
  """gnt-cluster epo"""
310
  master = qa_config.GetMasterNode()
311

    
312
  # Assert that OOB is unavailable for all nodes
313
  result_output = GetCommandOutput(master.primary,
314
                                   "gnt-node list --verbose --no-headers -o"
315
                                   " powered")
316
  AssertEqual(compat.all(powered == "(unavail)"
317
                         for powered in result_output.splitlines()), True)
318

    
319
  # Conflicting
320
  AssertCommand(["gnt-cluster", "epo", "--groups", "--all"], fail=True)
321
  # --all doesn't expect arguments
322
  AssertCommand(["gnt-cluster", "epo", "--all", "some_arg"], fail=True)
323

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

    
327
  # This shouldn't fail
328
  AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
329

    
330
  # All instances should have been stopped now
331
  result_output = GetCommandOutput(master.primary,
332
                                   "gnt-instance list --no-headers -o status")
333
  # ERROR_down because the instance is stopped but not recorded as such
334
  AssertEqual(compat.all(status == "ERROR_down"
335
                         for status in result_output.splitlines()), True)
336

    
337
  # Now start everything again
338
  AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
339

    
340
  # All instances should have been started now
341
  result_output = GetCommandOutput(master.primary,
342
                                   "gnt-instance list --no-headers -o status")
343
  AssertEqual(compat.all(status == "running"
344
                         for status in result_output.splitlines()), True)
345

    
346

    
347
def TestClusterVerify():
348
  """gnt-cluster verify"""
349
  AssertCommand(_CLUSTER_VERIFY)
350
  AssertCommand(["gnt-cluster", "verify-disks"])
351

    
352

    
353
def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
354
  """gnt-cluster verify-disks with broken DRBD"""
355
  qa_daemon.TestPauseWatcher()
356

    
357
  try:
358
    info = qa_instance.GetInstanceInfo(instance.name)
359
    snode = inst_nodes[1]
360
    for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
361
      if idx % 2 == 0:
362
        break_drbd_cmd = \
363
          "(drbdsetup %d down >/dev/null 2>&1;" \
364
          " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
365
          (minor, minor)
366
      else:
367
        break_drbd_cmd = \
368
          "(drbdsetup %d detach >/dev/null 2>&1;" \
369
          " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
370
          (minor, minor)
371
      AssertCommand(break_drbd_cmd, node=snode)
372

    
373
    verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
374
                                     "gnt-cluster verify-disks")
375
    activation_msg = "Activating disks for instance '%s'" % instance.name
376
    if activation_msg not in verify_output:
377
      raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
378
                           " DRBD disks:\n%s" % verify_output)
379

    
380
    verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
381
                                     "gnt-cluster verify-disks")
382
    if activation_msg in verify_output:
383
      raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
384
                           " DRBD disks on second attempt:\n%s" % verify_output)
385

    
386
    AssertCommand(_CLUSTER_VERIFY)
387
  finally:
388
    qa_daemon.TestResumeWatcher()
389

    
390

    
391
def TestJobqueue():
392
  """gnt-debug test-jobqueue"""
393
  AssertCommand(["gnt-debug", "test-jobqueue"])
394

    
395

    
396
def TestDelay(node):
397
  """gnt-debug delay"""
398
  AssertCommand(["gnt-debug", "delay", "1"])
399
  AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
400
  AssertCommand(["gnt-debug", "delay", "--no-master",
401
                 "-n", node.primary, "1"])
402

    
403

    
404
def TestClusterReservedLvs():
405
  """gnt-cluster reserved lvs"""
406
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
407
  lvname = _QA_LV_PREFIX + "test"
408
  lvfullname = "/".join([vgname, lvname])
409
  for fail, cmd in [
410
    (False, _CLUSTER_VERIFY),
411
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
412
    (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
413
    (True, _CLUSTER_VERIFY),
414
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
415
             "%s,.*/other-test" % lvfullname]),
416
    (False, _CLUSTER_VERIFY),
417
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
418
             ".*/%s.*" % _QA_LV_PREFIX]),
419
    (False, _CLUSTER_VERIFY),
420
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
421
    (True, _CLUSTER_VERIFY),
422
    (False, ["lvremove", "-f", lvfullname]),
423
    (False, _CLUSTER_VERIFY),
424
    ]:
425
    AssertCommand(cmd, fail=fail)
426

    
427

    
428
def TestClusterModifyEmpty():
429
  """gnt-cluster modify"""
430
  AssertCommand(["gnt-cluster", "modify"], fail=True)
431

    
432

    
433
def TestClusterModifyDisk():
434
  """gnt-cluster modify -D"""
435
  for param in _FAIL_PARAMS:
436
    AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
437

    
438

    
439
def TestClusterModifyDiskTemplates():
440
  """gnt-cluster modify --enabled-disk-templates=..."""
441
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
442
  default_disk_template = qa_config.GetDefaultDiskTemplate()
443

    
444
  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
445
                                           enabled_disk_templates)
446
  _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
447

    
448
  _RestoreEnabledDiskTemplates()
449
  nodes = qa_config.AcquireManyNodes(2)
450

    
451
  instance_template = enabled_disk_templates[0]
452
  instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
453

    
454
  _TestClusterModifyUnusedDiskTemplate(instance_template)
455
  _TestClusterModifyUsedDiskTemplate(instance_template,
456
                                     enabled_disk_templates)
457

    
458
  qa_instance.TestInstanceRemove(instance)
459
  _RestoreEnabledDiskTemplates()
460

    
461

    
462
def _RestoreEnabledDiskTemplates():
463
  """Sets the list of enabled disk templates back to the list of enabled disk
464
     templates from the QA configuration. This can be used to make sure that
465
     the tests that modify the list of disk templates do not interfere with
466
     other tests.
467

468
  """
469
  cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
470
         ",".join(qa_config.GetEnabledDiskTemplates())]
471

    
472
  if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
473
    vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
474
    cmd.append("--vg-name=%s" % vgname)
475

    
476
  AssertCommand(cmd, fail=False)
477

    
478

    
479
def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
480
                                             enabled_disk_templates):
481
  """Tests argument handling of 'gnt-cluster modify' with respect to
482
     the parameter '--enabled-disk-templates'. This test is independent
483
     of instances.
484

485
  """
486
  _RestoreEnabledDiskTemplates()
487

    
488
  # bogus templates
489
  AssertCommand(["gnt-cluster", "modify",
490
                 "--enabled-disk-templates=pinkbunny"],
491
                fail=True)
492

    
493
  # duplicate entries do no harm
494
  AssertCommand(
495
    ["gnt-cluster", "modify",
496
     "--enabled-disk-templates=%s,%s" %
497
      (default_disk_template, default_disk_template)],
498
    fail=False)
499

    
500
  if constants.DT_DRBD8 in enabled_disk_templates:
501
    # interaction with --drbd-usermode-helper option
502
    drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
503
    if not drbd_usermode_helper:
504
      drbd_usermode_helper = "/bin/true"
505
    # specifying a helper when drbd gets disabled is ok. Note that drbd still
506
    # has to be installed on the nodes in this case
507
    AssertCommand(["gnt-cluster", "modify",
508
                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
509
                   "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
510
                   fail=False)
511
    # specifying a helper when drbd is re-enabled
512
    AssertCommand(["gnt-cluster", "modify",
513
                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
514
                   "--enabled-disk-templates=%s" %
515
                     ",".join(enabled_disk_templates)],
516
                  fail=False)
517

    
518

    
519
def _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates):
520
  """Tests argument handling of 'gnt-cluster modify' with respect to
521
     the parameter '--enabled-disk-templates' and '--vg-name'. This test is
522
     independent of instances.
523

524
  """
525
  if not utils.IsLvmEnabled(enabled_disk_templates):
526
    # These tests only make sense if lvm is enabled for QA
527
    return
528

    
529
  # determine an LVM and a non-LVM disk template for the tests
530
  non_lvm_templates = list(set(enabled_disk_templates)
531
                           - set(utils.GetLvmDiskTemplates()))
532
  lvm_template = list(set(enabled_disk_templates)
533
                      .intersection(set(utils.GetLvmDiskTemplates())))[0]
534
  non_lvm_template = None
535
  if non_lvm_templates:
536
    non_lvm_template = non_lvm_templates[0]
537
  else:
538
    # If no non-lvm disk template is available for QA, choose 'diskless' and
539
    # hope for the best.
540
    non_lvm_template = constants.ST_DISKLESS
541

    
542
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
543

    
544
  # Clean start: unset volume group name, disable lvm storage
545
  AssertCommand(
546
    ["gnt-cluster", "modify",
547
     "--enabled-disk-templates=%s" % non_lvm_template,
548
     "--vg-name="],
549
    fail=False)
550

    
551
  # Try to enable lvm, when no volume group is given
552
  AssertCommand(
553
    ["gnt-cluster", "modify",
554
     "--enabled-disk-templates=%s" % lvm_template],
555
    fail=True)
556

    
557
  # Set volume group, with lvm still disabled: just a warning
558
  AssertCommand(["gnt-cluster", "modify", "--vg-name=%s" % vgname], fail=False)
559

    
560
  # Try unsetting vg name and enabling lvm at the same time
561
  AssertCommand(
562
    ["gnt-cluster", "modify",
563
     "--enabled-disk-templates=%s" % lvm_template,
564
     "--vg-name="],
565
    fail=True)
566

    
567
  # Enable lvm with vg name present
568
  AssertCommand(
569
    ["gnt-cluster", "modify",
570
     "--enabled-disk-templates=%s" % lvm_template],
571
    fail=False)
572

    
573
  # Try unsetting vg name with lvm still enabled
574
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
575

    
576
  # Disable lvm with vg name still set
577
  AssertCommand(
578
    ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
579
    fail=False)
580

    
581
  # Try unsetting vg name with lvm disabled
582
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
583

    
584
  # Set vg name and enable lvm at the same time
585
  AssertCommand(
586
    ["gnt-cluster", "modify",
587
     "--enabled-disk-templates=%s" % lvm_template,
588
     "--vg-name=%s" % vgname],
589
    fail=False)
590

    
591
  # Unset vg name and disable lvm at the same time
592
  AssertCommand(
593
    ["gnt-cluster", "modify",
594
     "--enabled-disk-templates=%s" % non_lvm_template,
595
     "--vg-name="],
596
    fail=False)
597

    
598
  _RestoreEnabledDiskTemplates()
599

    
600

    
601
def _TestClusterModifyUsedDiskTemplate(instance_template,
602
                                       enabled_disk_templates):
603
  """Tests that disk templates that are currently in use by instances cannot
604
     be disabled on the cluster.
605

606
  """
607
  # If the list of enabled disk templates contains only one template
608
  # we need to add some other templates, because the list of enabled disk
609
  # templates can only be set to a non-empty list.
610
  new_disk_templates = list(set(enabled_disk_templates)
611
                              - set([instance_template]))
612
  if not new_disk_templates:
613
    new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
614
                                - set([instance_template]))
615
  AssertCommand(
616
    ["gnt-cluster", "modify",
617
     "--enabled-disk-templates=%s" %
618
       ",".join(new_disk_templates)],
619
    fail=True)
620

    
621

    
622
def _TestClusterModifyUnusedDiskTemplate(instance_template):
623
  """Tests that unused disk templates can be disabled safely."""
624
  all_disk_templates = constants.DISK_TEMPLATES
625
  if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
626
    all_disk_templates = list(set(all_disk_templates) -
627
                              set(utils.GetLvmDiskTemplates()))
628

    
629
  AssertCommand(
630
    ["gnt-cluster", "modify",
631
     "--enabled-disk-templates=%s" %
632
       ",".join(all_disk_templates)],
633
    fail=False)
634
  new_disk_templates = [instance_template]
635
  AssertCommand(
636
    ["gnt-cluster", "modify",
637
     "--enabled-disk-templates=%s" %
638
       ",".join(new_disk_templates)],
639
    fail=False)
640

    
641

    
642
def TestClusterModifyBe():
643
  """gnt-cluster modify -B"""
644
  for fail, cmd in [
645
    # max/min mem
646
    (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
647
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
648
    (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
649
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
650
    (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
651
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
652
    (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
653
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
654
    (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
655
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
656
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
657
    # vcpus
658
    (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
659
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
660
    (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
661
    (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
662
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
663
    # auto_balance
664
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
665
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
666
    (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
667
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
668
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
669
    ]:
670
    AssertCommand(cmd, fail=fail)
671

    
672
  # redo the original-requested BE parameters, if any
673
  bep = qa_config.get("backend-parameters", "")
674
  if bep:
675
    AssertCommand(["gnt-cluster", "modify", "-B", bep])
676

    
677

    
678
def _GetClusterIPolicy():
679
  """Return the run-time values of the cluster-level instance policy.
680

681
  @rtype: tuple
682
  @return: (policy, specs), where:
683
      - policy is a dictionary of the policy values, instance specs excluded
684
      - specs is a dictionary containing only the specs, using the internal
685
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
686

687
  """
688
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
689
  policy = info["Instance policy - limits for instances"]
690
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
691

    
692
  # Sanity checks
693
  assert "minmax" in ret_specs and "std" in ret_specs
694
  assert len(ret_specs["minmax"]) > 0
695
  assert len(ret_policy) > 0
696
  return (ret_policy, ret_specs)
697

    
698

    
699
def TestClusterModifyIPolicy():
700
  """gnt-cluster modify --ipolicy-*"""
701
  basecmd = ["gnt-cluster", "modify"]
702
  (old_policy, old_specs) = _GetClusterIPolicy()
703
  for par in ["vcpu-ratio", "spindle-ratio"]:
704
    curr_val = float(old_policy[par])
705
    test_values = [
706
      (True, 1.0),
707
      (True, 1.5),
708
      (True, 2),
709
      (False, "a"),
710
      # Restore the old value
711
      (True, curr_val),
712
      ]
713
    for (good, val) in test_values:
714
      cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
715
      AssertCommand(cmd, fail=not good)
716
      if good:
717
        curr_val = val
718
      # Check the affected parameter
719
      (eff_policy, eff_specs) = _GetClusterIPolicy()
720
      AssertEqual(float(eff_policy[par]), curr_val)
721
      # Check everything else
722
      AssertEqual(eff_specs, old_specs)
723
      for p in eff_policy.keys():
724
        if p == par:
725
          continue
726
        AssertEqual(eff_policy[p], old_policy[p])
727

    
728
  # Disk templates are treated slightly differently
729
  par = "disk-templates"
730
  disp_str = "allowed disk templates"
731
  curr_val = old_policy[disp_str]
732
  test_values = [
733
    (True, constants.DT_PLAIN),
734
    (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
735
    (False, "thisisnotadisktemplate"),
736
    (False, ""),
737
    # Restore the old value
738
    (True, curr_val.replace(" ", "")),
739
    ]
740
  for (good, val) in test_values:
741
    cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
742
    AssertCommand(cmd, fail=not good)
743
    if good:
744
      curr_val = val
745
    # Check the affected parameter
746
    (eff_policy, eff_specs) = _GetClusterIPolicy()
747
    AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
748
    # Check everything else
749
    AssertEqual(eff_specs, old_specs)
750
    for p in eff_policy.keys():
751
      if p == disp_str:
752
        continue
753
      AssertEqual(eff_policy[p], old_policy[p])
754

    
755

    
756
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
757
                         old_values=None):
758
  """Change instance specs.
759

760
  At most one of new_specs or diff_specs can be specified.
761

762
  @type new_specs: dict
763
  @param new_specs: new complete specs, in the same format returned by
764
      L{_GetClusterIPolicy}
765
  @type diff_specs: dict
766
  @param diff_specs: partial specs, it can be an incomplete specifications, but
767
      if min/max specs are specified, their number must match the number of the
768
      existing specs
769
  @type fail: bool
770
  @param fail: if the change is expected to fail
771
  @type old_values: tuple
772
  @param old_values: (old_policy, old_specs), as returned by
773
      L{_GetClusterIPolicy}
774
  @return: same as L{_GetClusterIPolicy}
775

776
  """
777
  build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
778
  return qa_utils.TestSetISpecs(
779
    new_specs=new_specs, diff_specs=diff_specs,
780
    get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
781
    fail=fail, old_values=old_values)
782

    
783

    
784
def TestClusterModifyISpecs():
785
  """gnt-cluster modify --specs-*"""
786
  params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
787
  (cur_policy, cur_specs) = _GetClusterIPolicy()
788
  # This test assumes that there is only one min/max bound
789
  assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
790
  for par in params:
791
    test_values = [
792
      (True, 0, 4, 12),
793
      (True, 4, 4, 12),
794
      (True, 4, 12, 12),
795
      (True, 4, 4, 4),
796
      (False, 4, 0, 12),
797
      (False, 4, 16, 12),
798
      (False, 4, 4, 0),
799
      (False, 12, 4, 4),
800
      (False, 12, 4, 0),
801
      (False, "a", 4, 12),
802
      (False, 0, "a", 12),
803
      (False, 0, 4, "a"),
804
      # This is to restore the old values
805
      (True,
806
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
807
       cur_specs[constants.ISPECS_STD][par],
808
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
809
      ]
810
    for (good, mn, st, mx) in test_values:
811
      new_vals = {
812
        constants.ISPECS_MINMAX: [{
813
          constants.ISPECS_MIN: {par: mn},
814
          constants.ISPECS_MAX: {par: mx}
815
          }],
816
        constants.ISPECS_STD: {par: st}
817
        }
818
      cur_state = (cur_policy, cur_specs)
819
      # We update cur_specs, as we've copied the values to restore already
820
      (cur_policy, cur_specs) = TestClusterSetISpecs(
821
        diff_specs=new_vals, fail=not good, old_values=cur_state)
822

    
823
    # Get the ipolicy command
824
    mnode = qa_config.GetMasterNode()
825
    initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
826
    modcmd = ["gnt-cluster", "modify"]
827
    opts = initcmd.split()
828
    assert opts[0:2] == ["gnt-cluster", "init"]
829
    for k in range(2, len(opts) - 1):
830
      if opts[k].startswith("--ipolicy-"):
831
        assert k + 2 <= len(opts)
832
        modcmd.extend(opts[k:k + 2])
833
    # Re-apply the ipolicy (this should be a no-op)
834
    AssertCommand(modcmd)
835
    new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
836
    AssertEqual(initcmd, new_initcmd)
837

    
838

    
839
def TestClusterInfo():
840
  """gnt-cluster info"""
841
  AssertCommand(["gnt-cluster", "info"])
842

    
843

    
844
def TestClusterRedistConf():
845
  """gnt-cluster redist-conf"""
846
  AssertCommand(["gnt-cluster", "redist-conf"])
847

    
848

    
849
def TestClusterGetmaster():
850
  """gnt-cluster getmaster"""
851
  AssertCommand(["gnt-cluster", "getmaster"])
852

    
853

    
854
def TestClusterVersion():
855
  """gnt-cluster version"""
856
  AssertCommand(["gnt-cluster", "version"])
857

    
858

    
859
def TestClusterRenewCrypto():
860
  """gnt-cluster renew-crypto"""
861
  master = qa_config.GetMasterNode()
862

    
863
  # Conflicting options
864
  cmd = ["gnt-cluster", "renew-crypto", "--force",
865
         "--new-cluster-certificate", "--new-confd-hmac-key"]
866
  conflicting = [
867
    ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
868
    ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
869
    ]
870
  for i in conflicting:
871
    AssertCommand(cmd + i, fail=True)
872

    
873
  # Invalid RAPI certificate
874
  cmd = ["gnt-cluster", "renew-crypto", "--force",
875
         "--rapi-certificate=/dev/null"]
876
  AssertCommand(cmd, fail=True)
877

    
878
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
879
                                         pathutils.RAPI_CERT_FILE)
880
  try:
881
    # Custom RAPI certificate
882
    fh = tempfile.NamedTemporaryFile()
883

    
884
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
885
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
886

    
887
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
888

    
889
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
890
    try:
891
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
892
                     "--rapi-certificate=%s" % tmpcert])
893
    finally:
894
      AssertCommand(["rm", "-f", tmpcert])
895

    
896
    # Custom cluster domain secret
897
    cds_fh = tempfile.NamedTemporaryFile()
898
    cds_fh.write(utils.GenerateSecret())
899
    cds_fh.write("\n")
900
    cds_fh.flush()
901

    
902
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
903
    try:
904
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
905
                     "--cluster-domain-secret=%s" % tmpcds])
906
    finally:
907
      AssertCommand(["rm", "-f", tmpcds])
908

    
909
    # Normal case
910
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
911
                   "--new-cluster-certificate", "--new-confd-hmac-key",
912
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
913

    
914
    # Restore RAPI certificate
915
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
916
                   "--rapi-certificate=%s" % rapi_cert_backup])
917
  finally:
918
    AssertCommand(["rm", "-f", rapi_cert_backup])
919

    
920

    
921
def TestClusterBurnin():
922
  """Burnin"""
923
  master = qa_config.GetMasterNode()
924

    
925
  options = qa_config.get("options", {})
926
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
927
  parallel = options.get("burnin-in-parallel", False)
928
  check_inst = options.get("burnin-check-instances", False)
929
  do_rename = options.get("burnin-rename", "")
930
  do_reboot = options.get("burnin-reboot", True)
931
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
932

    
933
  # Get as many instances as we need
934
  instances = []
935
  try:
936
    try:
937
      num = qa_config.get("options", {}).get("burnin-instances", 1)
938
      for _ in range(0, num):
939
        instances.append(qa_config.AcquireInstance())
940
    except qa_error.OutOfInstancesError:
941
      print "Not enough instances, continuing anyway."
942

    
943
    if len(instances) < 1:
944
      raise qa_error.Error("Burnin needs at least one instance")
945

    
946
    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
947
    try:
948
      disks = qa_config.GetDiskOptions()
949
      # Run burnin
950
      cmd = [script,
951
             "--os=%s" % qa_config.get("os"),
952
             "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
953
             "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
954
             "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
955
             "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
956
             "--disk-template=%s" % disk_template]
957
      if parallel:
958
        cmd.append("--parallel")
959
        cmd.append("--early-release")
960
      if check_inst:
961
        cmd.append("--http-check")
962
      if do_rename:
963
        cmd.append("--rename=%s" % do_rename)
964
      if not do_reboot:
965
        cmd.append("--no-reboot")
966
      else:
967
        cmd.append("--reboot-types=%s" % ",".join(reboot_types))
968
      cmd += [inst.name for inst in instances]
969
      AssertCommand(cmd)
970
    finally:
971
      AssertCommand(["rm", "-f", script])
972

    
973
  finally:
974
    for inst in instances:
975
      inst.Release()
976

    
977

    
978
def TestClusterMasterFailover():
979
  """gnt-cluster master-failover"""
980
  master = qa_config.GetMasterNode()
981
  failovermaster = qa_config.AcquireNode(exclude=master)
982

    
983
  cmd = ["gnt-cluster", "master-failover"]
984
  try:
985
    AssertCommand(cmd, node=failovermaster)
986
    # Back to original master node
987
    AssertCommand(cmd, node=master)
988
  finally:
989
    failovermaster.Release()
990

    
991

    
992
def _NodeQueueDrainFile(node):
993
  """Returns path to queue drain file for a node.
994

995
  """
996
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
997

    
998

    
999
def _AssertDrainFile(node, **kwargs):
1000
  """Checks for the queue drain file.
1001

1002
  """
1003
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1004

    
1005

    
1006
def TestClusterMasterFailoverWithDrainedQueue():
1007
  """gnt-cluster master-failover with drained queue"""
1008
  master = qa_config.GetMasterNode()
1009
  failovermaster = qa_config.AcquireNode(exclude=master)
1010

    
1011
  # Ensure queue is not drained
1012
  for node in [master, failovermaster]:
1013
    _AssertDrainFile(node, fail=True)
1014

    
1015
  # Drain queue on failover master
1016
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1017
                node=failovermaster)
1018

    
1019
  cmd = ["gnt-cluster", "master-failover"]
1020
  try:
1021
    _AssertDrainFile(failovermaster)
1022
    AssertCommand(cmd, node=failovermaster)
1023
    _AssertDrainFile(master, fail=True)
1024
    _AssertDrainFile(failovermaster, fail=True)
1025

    
1026
    # Back to original master node
1027
    AssertCommand(cmd, node=master)
1028
  finally:
1029
    failovermaster.Release()
1030

    
1031
  # Ensure queue is not drained
1032
  for node in [master, failovermaster]:
1033
    _AssertDrainFile(node, fail=True)
1034

    
1035

    
1036
def TestClusterCopyfile():
1037
  """gnt-cluster copyfile"""
1038
  master = qa_config.GetMasterNode()
1039

    
1040
  uniqueid = utils.NewUUID()
1041

    
1042
  # Create temporary file
1043
  f = tempfile.NamedTemporaryFile()
1044
  f.write(uniqueid)
1045
  f.flush()
1046
  f.seek(0)
1047

    
1048
  # Upload file to master node
1049
  testname = qa_utils.UploadFile(master.primary, f.name)
1050
  try:
1051
    # Copy file to all nodes
1052
    AssertCommand(["gnt-cluster", "copyfile", testname])
1053
    _CheckFileOnAllNodes(testname, uniqueid)
1054
  finally:
1055
    _RemoveFileFromAllNodes(testname)
1056

    
1057

    
1058
def TestClusterCommand():
1059
  """gnt-cluster command"""
1060
  uniqueid = utils.NewUUID()
1061
  rfile = "/tmp/gnt%s" % utils.NewUUID()
1062
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1063
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1064
                              "%s >%s" % (rcmd, rfile)])
1065

    
1066
  try:
1067
    AssertCommand(cmd)
1068
    _CheckFileOnAllNodes(rfile, uniqueid)
1069
  finally:
1070
    _RemoveFileFromAllNodes(rfile)
1071

    
1072

    
1073
def TestClusterDestroy():
1074
  """gnt-cluster destroy"""
1075
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1076

    
1077

    
1078
def TestClusterRepairDiskSizes():
1079
  """gnt-cluster repair-disk-sizes"""
1080
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1081

    
1082

    
1083
def TestSetExclStorCluster(newvalue):
1084
  """Set the exclusive_storage node parameter at the cluster level.
1085

1086
  @type newvalue: bool
1087
  @param newvalue: New value of exclusive_storage
1088
  @rtype: bool
1089
  @return: The old value of exclusive_storage
1090

1091
  """
1092
  es_path = ["Default node parameters", "exclusive_storage"]
1093
  oldvalue = _GetClusterField(es_path)
1094
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1095
                 "exclusive_storage=%s" % newvalue])
1096
  effvalue = _GetClusterField(es_path)
1097
  if effvalue != newvalue:
1098
    raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1099
                         " of %s" % (effvalue, newvalue))
1100
  qa_config.SetExclusiveStorage(newvalue)
1101
  return oldvalue
1102

    
1103

    
1104
def TestExclStorSharedPv(node):
1105
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
1106

1107
  """
1108
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1109
  lvname1 = _QA_LV_PREFIX + "vol1"
1110
  lvname2 = _QA_LV_PREFIX + "vol2"
1111
  node_name = node.primary
1112
  AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1113
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1114
  AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1115
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1116
                                         constants.CV_ENODEORPHANLV])
1117
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1118
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1119
  AssertClusterVerify()