Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ b2525349

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_utils
37
import qa_error
38
import qa_instance
39

    
40
from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41

    
42

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

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

    
49

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

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

    
57

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

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

    
66

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

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

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

    
84

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

    
88

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

    
103

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

    
111

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

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

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

    
138

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

    
145

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

    
152

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

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

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

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

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

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

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

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

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

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

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

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

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

    
226
  AssertCommand(cmd)
227

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

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

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

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

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

    
253

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

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

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

    
272

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

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

    
282
  AssertCommand(_CLUSTER_VERIFY, fail=True)
283

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

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

    
292
    AssertCommand(_CLUSTER_VERIFY, fail=True)
293

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

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

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

    
304

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

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

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

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

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

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

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

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

    
343

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

    
349

    
350
# pylint: disable=W0613
351
def TestClusterVerifyDisksBrokenDRBD(instance, inst_nodes):
352
  """gnt-cluster verify-disks with broken DRBD"""
353
  pass
354

    
355
# FIXME (thomasth): reenable once it works (see issue 516!)
356
# qa_daemon.TestPauseWatcher()
357
#
358
# try:
359
#   info = qa_instance.GetInstanceInfo(instance.name)
360
#   snode = inst_nodes[1]
361
#   for idx, minor in enumerate(info["drbd-minors"][snode.primary]):
362
#     if idx % 2 == 0:
363
#       break_drbd_cmd = \
364
#         "(drbdsetup %d down >/dev/null 2>&1;" \
365
#         " drbdsetup down resource%d >/dev/null 2>&1) || /bin/true" % \
366
#         (minor, minor)
367
#     else:
368
#       break_drbd_cmd = \
369
#         "(drbdsetup %d detach >/dev/null 2>&1;" \
370
#         " drbdsetup detach %d >/dev/null 2>&1) || /bin/true" % \
371
#         (minor, minor)
372
#     AssertCommand(break_drbd_cmd, node=snode)
373
#
374
#   verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
375
#                                    "gnt-cluster verify-disks")
376
#   activation_msg = "Activating disks for instance '%s'" % instance.name
377
#   if activation_msg not in verify_output:
378
#     raise qa_error.Error("gnt-cluster verify-disks did not activate broken"
379
#                          " DRBD disks:\n%s" % verify_output)
380
#
381
#   verify_output = GetCommandOutput(qa_config.GetMasterNode().primary,
382
#                                    "gnt-cluster verify-disks")
383
#   if activation_msg in verify_output:
384
#     raise qa_error.Error("gnt-cluster verify-disks wants to activate broken"
385
#                          " DRBD disks on second attempt:\n%s" % verify_output)
386
#
387
#   AssertCommand(_CLUSTER_VERIFY)
388
# finally:
389
#   qa_daemon.TestResumeWatcher()
390

    
391

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

    
396

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

    
404

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

    
428

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

    
433

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

    
439

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

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

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

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

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

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

    
462

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

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

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

    
477
  AssertCommand(cmd, fail=False)
478

    
479

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

486
  """
487
  _RestoreEnabledDiskTemplates()
488

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

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

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

    
519

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
599
  _RestoreEnabledDiskTemplates()
600

    
601

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

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

    
622

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

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

    
642

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

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

    
678

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

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

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

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

    
699

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

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

    
756

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

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

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

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

    
784

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

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

    
839

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

    
844

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

    
849

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

    
854

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

    
859

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

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

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

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

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

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

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

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

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

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

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

    
921

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

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

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

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

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

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

    
978

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

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

    
992

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

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

    
999

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

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

    
1006

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

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

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

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

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

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

    
1036

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

    
1041
  uniqueid = utils.NewUUID()
1042

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

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

    
1058

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

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

    
1073

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

    
1078

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

    
1083

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

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

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

    
1104

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

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