Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ 0c2cfb97

History | View | Annotate | Download (34.4 kB)

1
#
2
#
3

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

    
21

    
22
"""Cluster related QA tests.
23

24
"""
25

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

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

    
35
import qa_config
36
import qa_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
  vgname = qa_config.get("vg-name", constants.DEFAULT_VG)
429
  AssertCommand(
430
    ["gnt-cluster", "modify",
431
     "--enabled-disk-templates=%s" %
432
       ",".join(qa_config.GetEnabledDiskTemplates()),
433
     "--vg-name=%s" % vgname],
434
    fail=False)
435

    
436

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

443
  """
444
  _RestoreEnabledDiskTemplates()
445

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

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

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

    
476

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
556
  _RestoreEnabledDiskTemplates()
557

    
558

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

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

    
579

    
580
def _TestClusterModifyUnusedDiskTemplate(instance_template):
581
  """Tests that unused disk templates can be disabled safely."""
582
  all_disk_templates = constants.DISK_TEMPLATES
583
  AssertCommand(
584
    ["gnt-cluster", "modify",
585
     "--enabled-disk-templates=%s" %
586
       ",".join(all_disk_templates)],
587
    fail=False)
588
  new_disk_templates = [instance_template]
589
  AssertCommand(
590
    ["gnt-cluster", "modify",
591
     "--enabled-disk-templates=%s" %
592
       ",".join(new_disk_templates)],
593
    fail=False)
594

    
595

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

    
626
  # redo the original-requested BE parameters, if any
627
  bep = qa_config.get("backend-parameters", "")
628
  if bep:
629
    AssertCommand(["gnt-cluster", "modify", "-B", bep])
630

    
631

    
632
def _GetClusterIPolicy():
633
  """Return the run-time values of the cluster-level instance policy.
634

635
  @rtype: tuple
636
  @return: (policy, specs), where:
637
      - policy is a dictionary of the policy values, instance specs excluded
638
      - specs is a dictionary containing only the specs, using the internal
639
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
640

641
  """
642
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
643
  policy = info["Instance policy - limits for instances"]
644
  (ret_policy, ret_specs) = qa_utils.ParseIPolicy(policy)
645

    
646
  # Sanity checks
647
  assert "minmax" in ret_specs and "std" in ret_specs
648
  assert len(ret_specs["minmax"]) > 0
649
  assert len(ret_policy) > 0
650
  return (ret_policy, ret_specs)
651

    
652

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

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

    
709

    
710
def TestClusterSetISpecs(new_specs=None, diff_specs=None, fail=False,
711
                         old_values=None):
712
  """Change instance specs.
713

714
  At most one of new_specs or diff_specs can be specified.
715

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

730
  """
731
  build_cmd = lambda opts: ["gnt-cluster", "modify"] + opts
732
  return qa_utils.TestSetISpecs(
733
    new_specs=new_specs, diff_specs=diff_specs,
734
    get_policy_fn=_GetClusterIPolicy, build_cmd_fn=build_cmd,
735
    fail=fail, old_values=old_values)
736

    
737

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

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

    
792

    
793
def TestClusterInfo():
794
  """gnt-cluster info"""
795
  AssertCommand(["gnt-cluster", "info"])
796

    
797

    
798
def TestClusterRedistConf():
799
  """gnt-cluster redist-conf"""
800
  AssertCommand(["gnt-cluster", "redist-conf"])
801

    
802

    
803
def TestClusterGetmaster():
804
  """gnt-cluster getmaster"""
805
  AssertCommand(["gnt-cluster", "getmaster"])
806

    
807

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

    
812

    
813
def TestClusterRenewCrypto():
814
  """gnt-cluster renew-crypto"""
815
  master = qa_config.GetMasterNode()
816

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

    
827
  # Invalid RAPI certificate
828
  cmd = ["gnt-cluster", "renew-crypto", "--force",
829
         "--rapi-certificate=/dev/null"]
830
  AssertCommand(cmd, fail=True)
831

    
832
  rapi_cert_backup = qa_utils.BackupFile(master.primary,
833
                                         pathutils.RAPI_CERT_FILE)
834
  try:
835
    # Custom RAPI certificate
836
    fh = tempfile.NamedTemporaryFile()
837

    
838
    # Ensure certificate doesn't cause "gnt-cluster verify" to complain
839
    validity = constants.SSL_CERT_EXPIRATION_WARN * 3
840

    
841
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
842

    
843
    tmpcert = qa_utils.UploadFile(master.primary, fh.name)
844
    try:
845
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
846
                     "--rapi-certificate=%s" % tmpcert])
847
    finally:
848
      AssertCommand(["rm", "-f", tmpcert])
849

    
850
    # Custom cluster domain secret
851
    cds_fh = tempfile.NamedTemporaryFile()
852
    cds_fh.write(utils.GenerateSecret())
853
    cds_fh.write("\n")
854
    cds_fh.flush()
855

    
856
    tmpcds = qa_utils.UploadFile(master.primary, cds_fh.name)
857
    try:
858
      AssertCommand(["gnt-cluster", "renew-crypto", "--force",
859
                     "--cluster-domain-secret=%s" % tmpcds])
860
    finally:
861
      AssertCommand(["rm", "-f", tmpcds])
862

    
863
    # Normal case
864
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
865
                   "--new-cluster-certificate", "--new-confd-hmac-key",
866
                   "--new-rapi-certificate", "--new-cluster-domain-secret"])
867

    
868
    # Restore RAPI certificate
869
    AssertCommand(["gnt-cluster", "renew-crypto", "--force",
870
                   "--rapi-certificate=%s" % rapi_cert_backup])
871
  finally:
872
    AssertCommand(["rm", "-f", rapi_cert_backup])
873

    
874

    
875
def TestClusterBurnin():
876
  """Burnin"""
877
  master = qa_config.GetMasterNode()
878

    
879
  options = qa_config.get("options", {})
880
  disk_template = options.get("burnin-disk-template", constants.DT_DRBD8)
881
  parallel = options.get("burnin-in-parallel", False)
882
  check_inst = options.get("burnin-check-instances", False)
883
  do_rename = options.get("burnin-rename", "")
884
  do_reboot = options.get("burnin-reboot", True)
885
  reboot_types = options.get("reboot-types", constants.REBOOT_TYPES)
886

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

    
897
    if len(instances) < 1:
898
      raise qa_error.Error("Burnin needs at least one instance")
899

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

    
927
  finally:
928
    for inst in instances:
929
      inst.Release()
930

    
931

    
932
def TestClusterMasterFailover():
933
  """gnt-cluster master-failover"""
934
  master = qa_config.GetMasterNode()
935
  failovermaster = qa_config.AcquireNode(exclude=master)
936

    
937
  cmd = ["gnt-cluster", "master-failover"]
938
  try:
939
    AssertCommand(cmd, node=failovermaster)
940
    # Back to original master node
941
    AssertCommand(cmd, node=master)
942
  finally:
943
    failovermaster.Release()
944

    
945

    
946
def _NodeQueueDrainFile(node):
947
  """Returns path to queue drain file for a node.
948

949
  """
950
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
951

    
952

    
953
def _AssertDrainFile(node, **kwargs):
954
  """Checks for the queue drain file.
955

956
  """
957
  AssertCommand(["test", "-f", _NodeQueueDrainFile(node)], node=node, **kwargs)
958

    
959

    
960
def TestClusterMasterFailoverWithDrainedQueue():
961
  """gnt-cluster master-failover with drained queue"""
962
  master = qa_config.GetMasterNode()
963
  failovermaster = qa_config.AcquireNode(exclude=master)
964

    
965
  # Ensure queue is not drained
966
  for node in [master, failovermaster]:
967
    _AssertDrainFile(node, fail=True)
968

    
969
  # Drain queue on failover master
970
  AssertCommand(["touch", _NodeQueueDrainFile(failovermaster)],
971
                node=failovermaster)
972

    
973
  cmd = ["gnt-cluster", "master-failover"]
974
  try:
975
    _AssertDrainFile(failovermaster)
976
    AssertCommand(cmd, node=failovermaster)
977
    _AssertDrainFile(master, fail=True)
978
    _AssertDrainFile(failovermaster, fail=True)
979

    
980
    # Back to original master node
981
    AssertCommand(cmd, node=master)
982
  finally:
983
    failovermaster.Release()
984

    
985
  # Ensure queue is not drained
986
  for node in [master, failovermaster]:
987
    _AssertDrainFile(node, fail=True)
988

    
989

    
990
def TestClusterCopyfile():
991
  """gnt-cluster copyfile"""
992
  master = qa_config.GetMasterNode()
993

    
994
  uniqueid = utils.NewUUID()
995

    
996
  # Create temporary file
997
  f = tempfile.NamedTemporaryFile()
998
  f.write(uniqueid)
999
  f.flush()
1000
  f.seek(0)
1001

    
1002
  # Upload file to master node
1003
  testname = qa_utils.UploadFile(master.primary, f.name)
1004
  try:
1005
    # Copy file to all nodes
1006
    AssertCommand(["gnt-cluster", "copyfile", testname])
1007
    _CheckFileOnAllNodes(testname, uniqueid)
1008
  finally:
1009
    _RemoveFileFromAllNodes(testname)
1010

    
1011

    
1012
def TestClusterCommand():
1013
  """gnt-cluster command"""
1014
  uniqueid = utils.NewUUID()
1015
  rfile = "/tmp/gnt%s" % utils.NewUUID()
1016
  rcmd = utils.ShellQuoteArgs(["echo", "-n", uniqueid])
1017
  cmd = utils.ShellQuoteArgs(["gnt-cluster", "command",
1018
                              "%s >%s" % (rcmd, rfile)])
1019

    
1020
  try:
1021
    AssertCommand(cmd)
1022
    _CheckFileOnAllNodes(rfile, uniqueid)
1023
  finally:
1024
    _RemoveFileFromAllNodes(rfile)
1025

    
1026

    
1027
def TestClusterDestroy():
1028
  """gnt-cluster destroy"""
1029
  AssertCommand(["gnt-cluster", "destroy", "--yes-do-it"])
1030

    
1031

    
1032
def TestClusterRepairDiskSizes():
1033
  """gnt-cluster repair-disk-sizes"""
1034
  AssertCommand(["gnt-cluster", "repair-disk-sizes"])
1035

    
1036

    
1037
def TestSetExclStorCluster(newvalue):
1038
  """Set the exclusive_storage node parameter at the cluster level.
1039

1040
  @type newvalue: bool
1041
  @param newvalue: New value of exclusive_storage
1042
  @rtype: bool
1043
  @return: The old value of exclusive_storage
1044

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

    
1057

    
1058
def TestExclStorSharedPv(node):
1059
  """cluster-verify reports LVs that share the same PV with exclusive_storage.
1060

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