Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ 3039e2dc

History | View | Annotate | Download (36.4 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
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
178
  cmd = [
179
    "gnt-cluster", "init",
180
    "--primary-ip-version=%d" % qa_config.get("primary_ip_version", 4),
181
    "--enabled-hypervisors=%s" % ",".join(qa_config.GetEnabledHypervisors()),
182
    "--enabled-disk-templates=%s" %
183
      ",".join(enabled_disk_templates),
184
    ]
185
  if constants.DT_FILE in enabled_disk_templates:
186
    cmd.append(
187
        "--file-storage-dir=%s" %
188
        qa_config.get("file-storage-dir", pathutils.DEFAULT_FILE_STORAGE_DIR))
189

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

    
198
  if master.secondary:
199
    cmd.append("--secondary-ip=%s" % master.secondary)
200

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

    
209
  master_netdev = qa_config.get("master-netdev", None)
210
  if master_netdev:
211
    cmd.append("--master-netdev=%s" % master_netdev)
212

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

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

    
226
  extra_args = qa_config.get("cluster-init-args")
227
  if extra_args:
228
    cmd.extend(extra_args)
229

    
230
  cmd.append(qa_config.get("name"))
231

    
232
  AssertCommand(cmd)
233

    
234
  cmd = ["gnt-cluster", "modify"]
235

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

    
245
  if len(cmd) > 2:
246
    AssertCommand(cmd)
247

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

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

    
259

    
260
def TestClusterRename():
261
  """gnt-cluster rename"""
262
  cmd = ["gnt-cluster", "rename", "-f"]
263

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

    
270
  for data in [
271
    cmd + [rename_target],
272
    _CLUSTER_VERIFY,
273
    cmd + [original_name],
274
    _CLUSTER_VERIFY,
275
    ]:
276
    AssertCommand(data)
277

    
278

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

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

    
288
  AssertCommand(_CLUSTER_VERIFY, fail=True)
289

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

    
294
  try:
295
    AssertCommand(["gnt-cluster", "modify", "--node-parameters",
296
                   "oob_program=%s" % oob_path_exists])
297

    
298
    AssertCommand(_CLUSTER_VERIFY, fail=True)
299

    
300
    AssertCommand(["chmod", "0500", oob_path_exists])
301
    AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
302

    
303
    AssertCommand(_CLUSTER_VERIFY)
304
  finally:
305
    AssertCommand(["gnt-cluster", "command", "rm", oob_path_exists])
306

    
307
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
308
                 "oob_program="])
309

    
310

    
311
def TestClusterEpo():
312
  """gnt-cluster epo"""
313
  master = qa_config.GetMasterNode()
314

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

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

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

    
330
  # This shouldn't fail
331
  AssertCommand(["gnt-cluster", "epo", "-f", "--all"])
332

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

    
340
  # Now start everything again
341
  AssertCommand(["gnt-cluster", "epo", "--on", "-f", "--all"])
342

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

    
349

    
350
def TestClusterVerify():
351
  """gnt-cluster verify"""
352
  AssertCommand(_CLUSTER_VERIFY)
353
  AssertCommand(["gnt-cluster", "verify-disks"])
354

    
355

    
356
def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
357
  """gnt-cluster verify-disks with broken DRBD"""
358
  qa_daemon.TestPauseWatcher()
359

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

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

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

    
389
    AssertCommand(_CLUSTER_VERIFY)
390
  finally:
391
    qa_daemon.TestResumeWatcher()
392

    
393

    
394
def TestJobqueue():
395
  """gnt-debug test-jobqueue"""
396
  AssertCommand(["gnt-debug", "test-jobqueue"])
397

    
398

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

    
406

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

    
430

    
431
def TestClusterModifyEmpty():
432
  """gnt-cluster modify"""
433
  AssertCommand(["gnt-cluster", "modify"], fail=True)
434

    
435

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

    
441

    
442
def TestClusterModifyDiskTemplates():
443
  """gnt-cluster modify --enabled-disk-templates=..."""
444
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
445
  default_disk_template = qa_config.GetDefaultDiskTemplate()
446

    
447
  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
448
                                           enabled_disk_templates)
449
  _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
450

    
451
  _RestoreEnabledDiskTemplates()
452
  nodes = qa_config.AcquireManyNodes(2)
453

    
454
  instance_template = enabled_disk_templates[0]
455
  instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
456

    
457
  _TestClusterModifyUnusedDiskTemplate(instance_template)
458
  _TestClusterModifyUsedDiskTemplate(instance_template,
459
                                     enabled_disk_templates)
460

    
461
  qa_instance.TestInstanceRemove(instance)
462
  _RestoreEnabledDiskTemplates()
463

    
464

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

471
  """
472
  cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
473
         ",".join(qa_config.GetEnabledDiskTemplates())]
474

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

    
479
  AssertCommand(cmd, fail=False)
480

    
481

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

488
  """
489
  _RestoreEnabledDiskTemplates()
490

    
491
  # bogus templates
492
  AssertCommand(["gnt-cluster", "modify",
493
                 "--enabled-disk-templates=pinkbunny"],
494
                fail=True)
495

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

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

    
521

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

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

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

    
545
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
546

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

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

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

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

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

    
576
  # Try unsetting vg name with lvm still enabled
577
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
578

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

    
584
  # Try unsetting vg name with lvm disabled
585
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
586

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

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

    
601
  _RestoreEnabledDiskTemplates()
602

    
603

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

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

    
624

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

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

    
644

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

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

    
680

    
681
def _GetClusterIPolicy():
682
  """Return the run-time values of the cluster-level instance policy.
683

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

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

    
695
  # Sanity checks
696
  assert "minmax" in ret_specs and "std" in ret_specs
697
  assert len(ret_specs["minmax"]) > 0
698
  assert len(ret_policy) > 0
699
  return (ret_policy, ret_specs)
700

    
701

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

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

    
758

    
759
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
760
                         old_values=None):
761
  """Change instance specs.
762

763
  At most one of new_specs or diff_specs can be specified.
764

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

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

    
786

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

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

    
841

    
842
def TestClusterInfo():
843
  """gnt-cluster info"""
844
  AssertCommand(["gnt-cluster", "info"])
845

    
846

    
847
def TestClusterRedistConf():
848
  """gnt-cluster redist-conf"""
849
  AssertCommand(["gnt-cluster", "redist-conf"])
850

    
851

    
852
def TestClusterGetmaster():
853
  """gnt-cluster getmaster"""
854
  AssertCommand(["gnt-cluster", "getmaster"])
855

    
856

    
857
def TestClusterVersion():
858
  """gnt-cluster version"""
859
  AssertCommand(["gnt-cluster", "version"])
860

    
861

    
862
def TestClusterRenewCrypto():
863
  """gnt-cluster renew-crypto"""
864
  master = qa_config.GetMasterNode()
865

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

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

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

    
887
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
888
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
889

    
890
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
891

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

    
899
    # Custom cluster domain secret
900
    cds_fh = tempfile.NamedTemporaryFile()
901
    cds_fh.write(utils.GenerateSecret())
902
    cds_fh.write("\n")
903
    cds_fh.flush()
904

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

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

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

    
923

    
924
def TestClusterBurnin():
925
  """Burnin"""
926
  master = qa_config.GetMasterNode()
927

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

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

    
946
    if len(instances) < 1:
947
      raise qa_error.Error("Burnin needs at least one instance")
948

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

    
976
  finally:
977
    for inst in instances:
978
      inst.Release()
979

    
980

    
981
def TestClusterMasterFailover():
982
  """gnt-cluster master-failover"""
983
  master = qa_config.GetMasterNode()
984
  failovermaster = qa_config.AcquireNode(exclude=master)
985

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

    
994

    
995
def _NodeQueueDrainFile(node):
996
  """Returns path to queue drain file for a node.
997

998
  """
999
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
1000

    
1001

    
1002
def _AssertDrainFile(node, **kwargs):
1003
  """Checks for the queue drain file.
1004

1005
  """
1006
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
1007

    
1008

    
1009
def TestClusterMasterFailoverWithDrainedQueue():
1010
  """gnt-cluster master-failover with drained queue"""
1011
  master = qa_config.GetMasterNode()
1012
  failovermaster = qa_config.AcquireNode(exclude=master)
1013

    
1014
  # Ensure queue is not drained
1015
  for node in [master, failovermaster]:
1016
    _AssertDrainFile(node, fail=True)
1017

    
1018
  # Drain queue on failover master
1019
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
1020
                node=failovermaster)
1021

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

    
1029
    # Back to original master node
1030
    AssertCommand(cmd, node=master)
1031
  finally:
1032
    failovermaster.Release()
1033

    
1034
  # Ensure queue is not drained
1035
  for node in [master, failovermaster]:
1036
    _AssertDrainFile(node, fail=True)
1037

    
1038

    
1039
def TestClusterCopyfile():
1040
  """gnt-cluster copyfile"""
1041
  master = qa_config.GetMasterNode()
1042

    
1043
  uniqueid = utils.NewUUID()
1044

    
1045
  # Create temporary file
1046
  f = tempfile.NamedTemporaryFile()
1047
  f.write(uniqueid)
1048
  f.flush()
1049
  f.seek(0)
1050

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

    
1060

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

    
1069
  try:
1070
    AssertCommand(cmd)
1071
    _CheckFileOnAllNodes(rfile, uniqueid)
1072
  finally:
1073
    _RemoveFileFromAllNodes(rfile)
1074

    
1075

    
1076
def TestClusterDestroy():
1077
  """gnt-cluster destroy"""
1078
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1079

    
1080

    
1081
def TestClusterRepairDiskSizes():
1082
  """gnt-cluster repair-disk-sizes"""
1083
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1084

    
1085

    
1086
def TestSetExclStorCluster(newvalue):
1087
  """Set the exclusive_storage node parameter at the cluster level.
1088

1089
  @type newvalue: bool
1090
  @param newvalue: New value of exclusive_storage
1091
  @rtype: bool
1092
  @return: The old value of exclusive_storage
1093

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

    
1106

    
1107
def TestExclStorSharedPv(node):
1108
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
1109

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