Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ d101b7be

History | View | Annotate | Download (34.7 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
def TestJobqueue():
351
  """gnt-debug test-jobqueue"""
352
  AssertCommand(["gnt-debug", "test-jobqueue"])
353

    
354

    
355
def TestDelay(node):
356
  """gnt-debug delay"""
357
  AssertCommand(["gnt-debug", "delay", "1"])
358
  AssertCommand(["gnt-debug", "delay", "--no-master", "1"])
359
  AssertCommand(["gnt-debug", "delay", "--no-master",
360
                 "-n", node.primary, "1"])
361

    
362

    
363
def TestClusterReservedLvs():
364
  """gnt-cluster reserved lvs"""
365
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
366
  lvname = _QA_LV_PREFIX + "test"
367
  lvfullname = "/".join([vgname, lvname])
368
  for fail, cmd in [
369
    (False, _CLUSTER_VERIFY),
370
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
371
    (False, ["lvcreate", "-L1G", "-n", lvname, vgname]),
372
    (True, _CLUSTER_VERIFY),
373
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
374
             "%s,.*/other-test" % lvfullname]),
375
    (False, _CLUSTER_VERIFY),
376
    (False, ["gnt-cluster", "modify", "--reserved-lvs",
377
             ".*/%s.*" % _QA_LV_PREFIX]),
378
    (False, _CLUSTER_VERIFY),
379
    (False, ["gnt-cluster", "modify", "--reserved-lvs", ""]),
380
    (True, _CLUSTER_VERIFY),
381
    (False, ["lvremove", "-f", lvfullname]),
382
    (False, _CLUSTER_VERIFY),
383
    ]:
384
    AssertCommand(cmd, fail=fail)
385

    
386

    
387
def TestClusterModifyEmpty():
388
  """gnt-cluster modify"""
389
  AssertCommand(["gnt-cluster", "modify"], fail=True)
390

    
391

    
392
def TestClusterModifyDisk():
393
  """gnt-cluster modify -D"""
394
  for param in _FAIL_PARAMS:
395
    AssertCommand(["gnt-cluster", "modify", "-D", param], fail=True)
396

    
397

    
398
def TestClusterModifyDiskTemplates():
399
  """gnt-cluster modify --enabled-disk-templates=..."""
400
  enabled_disk_templates = qa_config.GetEnabledDiskTemplates()
401
  default_disk_template = qa_config.GetDefaultDiskTemplate()
402

    
403
  _TestClusterModifyDiskTemplatesArguments(default_disk_template,
404
                                           enabled_disk_templates)
405
  _TestClusterModifyDiskTemplatesVgName(enabled_disk_templates)
406

    
407
  _RestoreEnabledDiskTemplates()
408
  nodes = qa_config.AcquireManyNodes(2)
409

    
410
  instance_template = enabled_disk_templates[0]
411
  instance = qa_instance.CreateInstanceByDiskTemplate(nodes, instance_template)
412

    
413
  _TestClusterModifyUnusedDiskTemplate(instance_template)
414
  _TestClusterModifyUsedDiskTemplate(instance_template,
415
                                     enabled_disk_templates)
416

    
417
  qa_instance.TestInstanceRemove(instance)
418
  _RestoreEnabledDiskTemplates()
419

    
420

    
421
def _RestoreEnabledDiskTemplates():
422
  """Sets the list of enabled disk templates back to the list of enabled disk
423
     templates from the QA configuration. This can be used to make sure that
424
     the tests that modify the list of disk templates do not interfere with
425
     other tests.
426

427
  """
428
  cmd = ["gnt-cluster", "modify", "--enabled-disk-templates=%s" %
429
         ",".join(qa_config.GetEnabledDiskTemplates())]
430

    
431
  if utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
432
    vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
433
    cmd.append("--vg-name=%s" % vgname)
434

    
435
  AssertCommand(cmd, fail=False)
436

    
437

    
438
def _TestClusterModifyDiskTemplatesArguments(default_disk_template,
439
                                             enabled_disk_templates):
440
  """Tests argument handling of 'gnt-cluster modify' with respect to
441
     the parameter '--enabled-disk-templates'. This test is independent
442
     of instances.
443

444
  """
445
  _RestoreEnabledDiskTemplates()
446

    
447
  # bogus templates
448
  AssertCommand(["gnt-cluster", "modify",
449
                 "--enabled-disk-templates=pinkbunny"],
450
                fail=True)
451

    
452
  # duplicate entries do no harm
453
  AssertCommand(
454
    ["gnt-cluster", "modify",
455
     "--enabled-disk-templates=%s,%s" %
456
      (default_disk_template, default_disk_template)],
457
    fail=False)
458

    
459
  if constants.DT_DRBD8 in enabled_disk_templates:
460
    # interaction with --drbd-usermode-helper option
461
    drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
462
    if not drbd_usermode_helper:
463
      drbd_usermode_helper = "/bin/true"
464
    # specifying a helper when drbd gets disabled is ok. Note that drbd still
465
    # has to be installed on the nodes in this case
466
    AssertCommand(["gnt-cluster", "modify",
467
                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
468
                   "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
469
                   fail=False)
470
    # specifying a helper when drbd is re-enabled
471
    AssertCommand(["gnt-cluster", "modify",
472
                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
473
                   "--enabled-disk-templates=%s" %
474
                     ",".join(enabled_disk_templates)],
475
                  fail=False)
476

    
477

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

483
  """
484
  if not utils.IsLvmEnabled(enabled_disk_templates):
485
    # These tests only make sense if lvm is enabled for QA
486
    return
487

    
488
  # determine an LVM and a non-LVM disk template for the tests
489
  non_lvm_templates = list(set(enabled_disk_templates)
490
                           - set(utils.GetLvmDiskTemplates()))
491
  lvm_template = list(set(enabled_disk_templates)
492
                      .intersection(set(utils.GetLvmDiskTemplates())))[0]
493
  non_lvm_template = None
494
  if non_lvm_templates:
495
    non_lvm_template = non_lvm_templates[0]
496
  else:
497
    # If no non-lvm disk template is available for QA, choose 'diskless' and
498
    # hope for the best.
499
    non_lvm_template = constants.ST_DISKLESS
500

    
501
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
502

    
503
  # Clean start: unset volume group name, disable lvm storage
504
  AssertCommand(
505
    ["gnt-cluster", "modify",
506
     "--enabled-disk-templates=%s" % non_lvm_template,
507
     "--vg-name="],
508
    fail=False)
509

    
510
  # Try to enable lvm, when no volume group is given
511
  AssertCommand(
512
    ["gnt-cluster", "modify",
513
     "--enabled-disk-templates=%s" % lvm_template],
514
    fail=True)
515

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

    
519
  # Try unsetting vg name and enabling lvm at the same time
520
  AssertCommand(
521
    ["gnt-cluster", "modify",
522
     "--enabled-disk-templates=%s" % lvm_template,
523
     "--vg-name="],
524
    fail=True)
525

    
526
  # Enable lvm with vg name present
527
  AssertCommand(
528
    ["gnt-cluster", "modify",
529
     "--enabled-disk-templates=%s" % lvm_template],
530
    fail=False)
531

    
532
  # Try unsetting vg name with lvm still enabled
533
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=True)
534

    
535
  # Disable lvm with vg name still set
536
  AssertCommand(
537
    ["gnt-cluster", "modify", "--enabled-disk-templates=%s" % non_lvm_template],
538
    fail=False)
539

    
540
  # Try unsetting vg name with lvm disabled
541
  AssertCommand(["gnt-cluster", "modify", "--vg-name="], fail=False)
542

    
543
  # Set vg name and enable lvm at the same time
544
  AssertCommand(
545
    ["gnt-cluster", "modify",
546
     "--enabled-disk-templates=%s" % lvm_template,
547
     "--vg-name=%s" % vgname],
548
    fail=False)
549

    
550
  # Unset vg name and disable lvm at the same time
551
  AssertCommand(
552
    ["gnt-cluster", "modify",
553
     "--enabled-disk-templates=%s" % non_lvm_template,
554
     "--vg-name="],
555
    fail=False)
556

    
557
  _RestoreEnabledDiskTemplates()
558

    
559

    
560
def _TestClusterModifyUsedDiskTemplate(instance_template,
561
                                       enabled_disk_templates):
562
  """Tests that disk templates that are currently in use by instances cannot
563
     be disabled on the cluster.
564

565
  """
566
  # If the list of enabled disk templates contains only one template
567
  # we need to add some other templates, because the list of enabled disk
568
  # templates can only be set to a non-empty list.
569
  new_disk_templates = list(set(enabled_disk_templates)
570
                              - set([instance_template]))
571
  if not new_disk_templates:
572
    new_disk_templates = list(set([constants.DT_DISKLESS, constants.DT_BLOCK])
573
                                - set([instance_template]))
574
  AssertCommand(
575
    ["gnt-cluster", "modify",
576
     "--enabled-disk-templates=%s" %
577
       ",".join(new_disk_templates)],
578
    fail=True)
579

    
580

    
581
def _TestClusterModifyUnusedDiskTemplate(instance_template):
582
  """Tests that unused disk templates can be disabled safely."""
583
  all_disk_templates = constants.DISK_TEMPLATES
584
  if not utils.IsLvmEnabled(qa_config.GetEnabledDiskTemplates()):
585
    all_disk_templates = list(set(all_disk_templates) -
586
                              set(utils.GetLvmDiskTemplates()))
587

    
588
  AssertCommand(
589
    ["gnt-cluster", "modify",
590
     "--enabled-disk-templates=%s" %
591
       ",".join(all_disk_templates)],
592
    fail=False)
593
  new_disk_templates = [instance_template]
594
  AssertCommand(
595
    ["gnt-cluster", "modify",
596
     "--enabled-disk-templates=%s" %
597
       ",".join(new_disk_templates)],
598
    fail=False)
599

    
600

    
601
def TestClusterModifyBe():
602
  """gnt-cluster modify -B"""
603
  for fail, cmd in [
604
    # max/min mem
605
    (False, ["gnt-cluster", "modify", "-B", "maxmem=256"]),
606
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
607
    (False, ["gnt-cluster", "modify", "-B", "minmem=256"]),
608
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
609
    (True, ["gnt-cluster", "modify", "-B", "maxmem=a"]),
610
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 256$'"]),
611
    (True, ["gnt-cluster", "modify", "-B", "minmem=a"]),
612
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 256$'"]),
613
    (False, ["gnt-cluster", "modify", "-B", "maxmem=128,minmem=128"]),
614
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *maxmem: 128$'"]),
615
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *minmem: 128$'"]),
616
    # vcpus
617
    (False, ["gnt-cluster", "modify", "-B", "vcpus=4"]),
618
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 4$'"]),
619
    (True, ["gnt-cluster", "modify", "-B", "vcpus=a"]),
620
    (False, ["gnt-cluster", "modify", "-B", "vcpus=1"]),
621
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *vcpus: 1$'"]),
622
    # auto_balance
623
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=False"]),
624
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: False$'"]),
625
    (True, ["gnt-cluster", "modify", "-B", "auto_balance=1"]),
626
    (False, ["gnt-cluster", "modify", "-B", "auto_balance=True"]),
627
    (False, ["sh", "-c", "gnt-cluster info|grep '^ *auto_balance: True$'"]),
628
    ]:
629
    AssertCommand(cmd, fail=fail)
630

    
631
  # redo the original-requested BE parameters, if any
632
  bep = qa_config.get("backend-parameters", "")
633
  if bep:
634
    AssertCommand(["gnt-cluster", "modify", "-B", bep])
635

    
636

    
637
def _GetClusterIPolicy():
638
  """Return the run-time values of the cluster-level instance policy.
639

640
  @rtype: tuple
641
  @return: (policy, specs), where:
642
      - policy is a dictionary of the policy values, instance specs excluded
643
      - specs is a dictionary containing only the specs, using the internal
644
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
645

646
  """
647
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
648
  policy = info["Instance policy - limits for instances"]
649
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
650

    
651
  # Sanity checks
652
  assert "minmax" in ret_specs and "std" in ret_specs
653
  assert len(ret_specs["minmax"]) > 0
654
  assert len(ret_policy) > 0
655
  return (ret_policy, ret_specs)
656

    
657

    
658
def TestClusterModifyIPolicy():
659
  """gnt-cluster modify --ipolicy-*"""
660
  basecmd = ["gnt-cluster", "modify"]
661
  (old_policy, old_specs) = _GetClusterIPolicy()
662
  for par in ["vcpu-ratio", "spindle-ratio"]:
663
    curr_val = float(old_policy[par])
664
    test_values = [
665
      (True, 1.0),
666
      (True, 1.5),
667
      (True, 2),
668
      (False, "a"),
669
      # Restore the old value
670
      (True, curr_val),
671
      ]
672
    for (good, val) in test_values:
673
      cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
674
      AssertCommand(cmd, fail=not good)
675
      if good:
676
        curr_val = val
677
      # Check the affected parameter
678
      (eff_policy, eff_specs) = _GetClusterIPolicy()
679
      AssertEqual(float(eff_policy[par]), curr_val)
680
      # Check everything else
681
      AssertEqual(eff_specs, old_specs)
682
      for p in eff_policy.keys():
683
        if p == par:
684
          continue
685
        AssertEqual(eff_policy[p], old_policy[p])
686

    
687
  # Disk templates are treated slightly differently
688
  par = "disk-templates"
689
  disp_str = "allowed disk templates"
690
  curr_val = old_policy[disp_str]
691
  test_values = [
692
    (True, constants.DT_PLAIN),
693
    (True, "%s,%s" % (constants.DT_PLAIN, constants.DT_DRBD8)),
694
    (False, "thisisnotadisktemplate"),
695
    (False, ""),
696
    # Restore the old value
697
    (True, curr_val.replace(" ", "")),
698
    ]
699
  for (good, val) in test_values:
700
    cmd = basecmd + ["--ipolicy-%s=%s" % (par, val)]
701
    AssertCommand(cmd, fail=not good)
702
    if good:
703
      curr_val = val
704
    # Check the affected parameter
705
    (eff_policy, eff_specs) = _GetClusterIPolicy()
706
    AssertEqual(eff_policy[disp_str].replace(" ", ""), curr_val)
707
    # Check everything else
708
    AssertEqual(eff_specs, old_specs)
709
    for p in eff_policy.keys():
710
      if p == disp_str:
711
        continue
712
      AssertEqual(eff_policy[p], old_policy[p])
713

    
714

    
715
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
716
                         old_values=None):
717
  """Change instance specs.
718

719
  At most one of new_specs or diff_specs can be specified.
720

721
  @type new_specs: dict
722
  @param new_specs: new complete specs, in the same format returned by
723
      L{_GetClusterIPolicy}
724
  @type diff_specs: dict
725
  @param diff_specs: partial specs, it can be an incomplete specifications, but
726
      if min/max specs are specified, their number must match the number of the
727
      existing specs
728
  @type fail: bool
729
  @param fail: if the change is expected to fail
730
  @type old_values: tuple
731
  @param old_values: (old_policy, old_specs), as returned by
732
      L{_GetClusterIPolicy}
733
  @return: same as L{_GetClusterIPolicy}
734

735
  """
736
  build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
737
  return qa_utils.TestSetISpecs(
738
    new_specs=new_specs, diff_specs=diff_specs,
739
    get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
740
    fail=fail, old_values=old_values)
741

    
742

    
743
def TestClusterModifyISpecs():
744
  """gnt-cluster modify --specs-*"""
745
  params = ["memory-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
746
  (cur_policy, cur_specs) = _GetClusterIPolicy()
747
  # This test assumes that there is only one min/max bound
748
  assert len(cur_specs[constants.ISPECS_MINMAX]) == 1
749
  for par in params:
750
    test_values = [
751
      (True, 0, 4, 12),
752
      (True, 4, 4, 12),
753
      (True, 4, 12, 12),
754
      (True, 4, 4, 4),
755
      (False, 4, 0, 12),
756
      (False, 4, 16, 12),
757
      (False, 4, 4, 0),
758
      (False, 12, 4, 4),
759
      (False, 12, 4, 0),
760
      (False, "a", 4, 12),
761
      (False, 0, "a", 12),
762
      (False, 0, 4, "a"),
763
      # This is to restore the old values
764
      (True,
765
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MIN][par],
766
       cur_specs[constants.ISPECS_STD][par],
767
       cur_specs[constants.ISPECS_MINMAX][0][constants.ISPECS_MAX][par])
768
      ]
769
    for (good, mn, st, mx) in test_values:
770
      new_vals = {
771
        constants.ISPECS_MINMAX: [{
772
          constants.ISPECS_MIN: {par: mn},
773
          constants.ISPECS_MAX: {par: mx}
774
          }],
775
        constants.ISPECS_STD: {par: st}
776
        }
777
      cur_state = (cur_policy, cur_specs)
778
      # We update cur_specs, as we've copied the values to restore already
779
      (cur_policy, cur_specs) = TestClusterSetISpecs(
780
        diff_specs=new_vals, fail=not good, old_values=cur_state)
781

    
782
    # Get the ipolicy command
783
    mnode = qa_config.GetMasterNode()
784
    initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
785
    modcmd = ["gnt-cluster", "modify"]
786
    opts = initcmd.split()
787
    assert opts[0:2] == ["gnt-cluster", "init"]
788
    for k in range(2, len(opts) - 1):
789
      if opts[k].startswith("--ipolicy-"):
790
        assert k + 2 <= len(opts)
791
        modcmd.extend(opts[k:k + 2])
792
    # Re-apply the ipolicy (this should be a no-op)
793
    AssertCommand(modcmd)
794
    new_initcmd = GetCommandOutput(mnode.primary, "gnt-cluster show-ispecs-cmd")
795
    AssertEqual(initcmd, new_initcmd)
796

    
797

    
798
def TestClusterInfo():
799
  """gnt-cluster info"""
800
  AssertCommand(["gnt-cluster", "info"])
801

    
802

    
803
def TestClusterRedistConf():
804
  """gnt-cluster redist-conf"""
805
  AssertCommand(["gnt-cluster", "redist-conf"])
806

    
807

    
808
def TestClusterGetmaster():
809
  """gnt-cluster getmaster"""
810
  AssertCommand(["gnt-cluster", "getmaster"])
811

    
812

    
813
def TestClusterVersion():
814
  """gnt-cluster version"""
815
  AssertCommand(["gnt-cluster", "version"])
816

    
817

    
818
def TestClusterRenewCrypto():
819
  """gnt-cluster renew-crypto"""
820
  master = qa_config.GetMasterNode()
821

    
822
  # Conflicting options
823
  cmd = ["gnt-cluster", "renew-crypto", "--force",
824
         "--new-cluster-certificate", "--new-confd-hmac-key"]
825
  conflicting = [
826
    ["--new-rapi-certificate", "--rapi-certificate=/dev/null"],
827
    ["--new-cluster-domain-secret", "--cluster-domain-secret=/dev/null"],
828
    ]
829
  for i in conflicting:
830
    AssertCommand(cmd + i, fail=True)
831

    
832
  # Invalid RAPI certificate
833
  cmd = ["gnt-cluster", "renew-crypto", "--force",
834
         "--rapi-certificate=/dev/null"]
835
  AssertCommand(cmd, fail=True)
836

    
837
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
838
                                         pathutils.RAPI_CERT_FILE)
839
  try:
840
    # Custom RAPI certificate
841
    fh = tempfile.NamedTemporaryFile()
842

    
843
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
844
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
845

    
846
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
847

    
848
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
849
    try:
850
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
851
                     "--rapi-certificate=%s" % tmpcert])
852
    finally:
853
      AssertCommand(["rm", "-f", tmpcert])
854

    
855
    # Custom cluster domain secret
856
    cds_fh = tempfile.NamedTemporaryFile()
857
    cds_fh.write(utils.GenerateSecret())
858
    cds_fh.write("\n")
859
    cds_fh.flush()
860

    
861
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
862
    try:
863
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
864
                     "--cluster-domain-secret=%s" % tmpcds])
865
    finally:
866
      AssertCommand(["rm", "-f", tmpcds])
867

    
868
    # Normal case
869
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
870
                   "--new-cluster-certificate", "--new-confd-hmac-key",
871
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
872

    
873
    # Restore RAPI certificate
874
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
875
                   "--rapi-certificate=%s" % rapi_cert_backup])
876
  finally:
877
    AssertCommand(["rm", "-f", rapi_cert_backup])
878

    
879

    
880
def TestClusterBurnin():
881
  """Burnin"""
882
  master = qa_config.GetMasterNode()
883

    
884
  options = qa_config.get("options", {})
885
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
886
  parallel = options.get("burnin-in-parallel", False)
887
  check_inst = options.get("burnin-check-instances", False)
888
  do_rename = options.get("burnin-rename", "")
889
  do_reboot = options.get("burnin-reboot", True)
890
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
891

    
892
  # Get as many instances as we need
893
  instances = []
894
  try:
895
    try:
896
      num = qa_config.get("options", {}).get("burnin-instances", 1)
897
      for _ in range(0, num):
898
        instances.append(qa_config.AcquireInstance())
899
    except qa_error.OutOfInstancesError:
900
      print "Not enough instances, continuing anyway."
901

    
902
    if len(instances) < 1:
903
      raise qa_error.Error("Burnin needs at least one instance")
904

    
905
    script = qa_utils.UploadFile(master.primary, "../tools/burnin")
906
    try:
907
      disks = qa_config.GetDiskOptions()
908
      # Run burnin
909
      cmd = [script,
910
             "--os=%s" % qa_config.get("os"),
911
             "--minmem-size=%s" % qa_config.get(constants.BE_MINMEM),
912
             "--maxmem-size=%s" % qa_config.get(constants.BE_MAXMEM),
913
             "--disk-size=%s" % ",".join([d.get("size") for d in disks]),
914
             "--disk-growth=%s" % ",".join([d.get("growth") for d in disks]),
915
             "--disk-template=%s" % disk_template]
916
      if parallel:
917
        cmd.append("--parallel")
918
        cmd.append("--early-release")
919
      if check_inst:
920
        cmd.append("--http-check")
921
      if do_rename:
922
        cmd.append("--rename=%s" % do_rename)
923
      if not do_reboot:
924
        cmd.append("--no-reboot")
925
      else:
926
        cmd.append("--reboot-types=%s" % ",".join(reboot_types))
927
      cmd += [inst.name for inst in instances]
928
      AssertCommand(cmd)
929
    finally:
930
      AssertCommand(["rm", "-f", script])
931

    
932
  finally:
933
    for inst in instances:
934
      inst.Release()
935

    
936

    
937
def TestClusterMasterFailover():
938
  """gnt-cluster master-failover"""
939
  master = qa_config.GetMasterNode()
940
  failovermaster = qa_config.AcquireNode(exclude=master)
941

    
942
  cmd = ["gnt-cluster", "master-failover"]
943
  try:
944
    AssertCommand(cmd, node=failovermaster)
945
    # Back to original master node
946
    AssertCommand(cmd, node=master)
947
  finally:
948
    failovermaster.Release()
949

    
950

    
951
def _NodeQueueDrainFile(node):
952
  """Returns path to queue drain file for a node.
953

954
  """
955
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
956

    
957

    
958
def _AssertDrainFile(node, **kwargs):
959
  """Checks for the queue drain file.
960

961
  """
962
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
963

    
964

    
965
def TestClusterMasterFailoverWithDrainedQueue():
966
  """gnt-cluster master-failover with drained queue"""
967
  master = qa_config.GetMasterNode()
968
  failovermaster = qa_config.AcquireNode(exclude=master)
969

    
970
  # Ensure queue is not drained
971
  for node in [master, failovermaster]:
972
    _AssertDrainFile(node, fail=True)
973

    
974
  # Drain queue on failover master
975
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
976
                node=failovermaster)
977

    
978
  cmd = ["gnt-cluster", "master-failover"]
979
  try:
980
    _AssertDrainFile(failovermaster)
981
    AssertCommand(cmd, node=failovermaster)
982
    _AssertDrainFile(master, fail=True)
983
    _AssertDrainFile(failovermaster, fail=True)
984

    
985
    # Back to original master node
986
    AssertCommand(cmd, node=master)
987
  finally:
988
    failovermaster.Release()
989

    
990
  # Ensure queue is not drained
991
  for node in [master, failovermaster]:
992
    _AssertDrainFile(node, fail=True)
993

    
994

    
995
def TestClusterCopyfile():
996
  """gnt-cluster copyfile"""
997
  master = qa_config.GetMasterNode()
998

    
999
  uniqueid = utils.NewUUID()
1000

    
1001
  # Create temporary file
1002
  f = tempfile.NamedTemporaryFile()
1003
  f.write(uniqueid)
1004
  f.flush()
1005
  f.seek(0)
1006

    
1007
  # Upload file to master node
1008
  testname = qa_utils.UploadFile(master.primary, f.name)
1009
  try:
1010
    # Copy file to all nodes
1011
    AssertCommand(["gnt-cluster", "copyfile", testname])
1012
    _CheckFileOnAllNodes(testname, uniqueid)
1013
  finally:
1014
    _RemoveFileFromAllNodes(testname)
1015

    
1016

    
1017
def TestClusterCommand():
1018
  """gnt-cluster command"""
1019
  uniqueid = utils.NewUUID()
1020
  rfile = "/tmp/gnt%s" % utils.NewUUID()
1021
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1022
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1023
                              "%s >%s" % (rcmd, rfile)])
1024

    
1025
  try:
1026
    AssertCommand(cmd)
1027
    _CheckFileOnAllNodes(rfile, uniqueid)
1028
  finally:
1029
    _RemoveFileFromAllNodes(rfile)
1030

    
1031

    
1032
def TestClusterDestroy():
1033
  """gnt-cluster destroy"""
1034
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1035

    
1036

    
1037
def TestClusterRepairDiskSizes():
1038
  """gnt-cluster repair-disk-sizes"""
1039
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1040

    
1041

    
1042
def TestSetExclStorCluster(newvalue):
1043
  """Set the exclusive_storage node parameter at the cluster level.
1044

1045
  @type newvalue: bool
1046
  @param newvalue: New value of exclusive_storage
1047
  @rtype: bool
1048
  @return: The old value of exclusive_storage
1049

1050
  """
1051
  es_path = ["Default node parameters", "exclusive_storage"]
1052
  oldvalue = _GetClusterField(es_path)
1053
  AssertCommand(["gnt-cluster", "modify", "--node-parameters",
1054
                 "exclusive_storage=%s" % newvalue])
1055
  effvalue = _GetClusterField(es_path)
1056
  if effvalue != newvalue:
1057
    raise qa_error.Error("exclusive_storage has the wrong value: %s instead"
1058
                         " of %s" % (effvalue, newvalue))
1059
  qa_config.SetExclusiveStorage(newvalue)
1060
  return oldvalue
1061

    
1062

    
1063
def TestExclStorSharedPv(node):
1064
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
1065

1066
  """
1067
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
1068
  lvname1 = _QA_LV_PREFIX + "vol1"
1069
  lvname2 = _QA_LV_PREFIX + "vol2"
1070
  node_name = node.primary
1071
  AssertCommand(["lvcreate", "-L1G", "-n", lvname1, vgname], node=node_name)
1072
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODEORPHANLV])
1073
  AssertCommand(["lvcreate", "-L1G", "-n", lvname2, vgname], node=node_name)
1074
  AssertClusterVerify(fail=True, errors=[constants.CV_ENODELVM,
1075
                                         constants.CV_ENODEORPHANLV])
1076
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname1])], node=node_name)
1077
  AssertCommand(["lvremove", "-f", "/".join([vgname, lvname2])], node=node_name)
1078
  AssertClusterVerify()