Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ 7af293d7

History | View | Annotate | Download (36.2 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
    ]
184

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

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

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

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

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

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

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

    
225
  cmd.append(qa_config.get("name"))
226

    
227
  AssertCommand(cmd)
228

    
229
  cmd = ["gnt-cluster", "modify"]
230

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

    
240
  if len(cmd) > 2:
241
    AssertCommand(cmd)
242

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

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

    
254

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

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

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

    
273

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

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

    
283
  AssertCommand(_CLUSTER_VERIFY, fail=True)
284

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

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

    
293
    AssertCommand(_CLUSTER_VERIFY, fail=True)
294

    
295
    AssertCommand(["chmod", "0500", oob_path_exists])
296
    AssertCommand(["gnt-cluster", "copyfile", oob_path_exists])
297

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

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

    
305

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

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

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

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

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

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

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

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

    
344

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

    
350

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

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

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

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

    
384
    AssertCommand(_CLUSTER_VERIFY)
385
  finally:
386
    qa_daemon.TestResumeWatcher()
387

    
388

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

    
393

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

    
401

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

    
425

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

    
430

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

    
436

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

    
442
  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
443
                                           enabled_disk_templates)
444
  _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
445

    
446
  _RestoreEnabledDiskTemplates()
447
  nodes = qa_config.AcquireManyNodes(2)
448

    
449
  instance_template = enabled_disk_templates[0]
450
  instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
451

    
452
  _TestClusterModifyUnusedDiskTemplate(instance_template)
453
  _TestClusterModifyUsedDiskTemplate(instance_template,
454
                                     enabled_disk_templates)
455

    
456
  qa_instance.TestInstanceRemove(instance)
457
  _RestoreEnabledDiskTemplates()
458

    
459

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

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

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

    
474
  AssertCommand(cmd, fail=False)
475

    
476

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

483
  """
484
  _RestoreEnabledDiskTemplates()
485

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

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

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

    
516

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

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

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

    
540
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
541

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

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

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

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

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

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

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

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

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

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

    
596
  _RestoreEnabledDiskTemplates()
597

    
598

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

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

    
619

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

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

    
639

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

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

    
675

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

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

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

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

    
696

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

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

    
753

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

758
  At most one of new_specs or diff_specs can be specified.
759

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

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

    
781

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

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

    
836

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

    
841

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

    
846

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

    
851

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

    
856

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

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

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

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

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

    
885
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
886

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

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

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

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

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

    
918

    
919
def TestClusterBurnin():
920
  """Burnin"""
921
  master = qa_config.GetMasterNode()
922

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

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

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

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

    
971
  finally:
972
    for inst in instances:
973
      inst.Release()
974

    
975

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

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

    
989

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

993
  """
994
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
995

    
996

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

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

    
1003

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

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

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

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

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

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

    
1033

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

    
1038
  uniqueid = utils.NewUUID()
1039

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

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

    
1055

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

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

    
1070

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

    
1075

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

    
1080

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

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

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

    
1101

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

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