Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ 912737ba

History | View | Annotate | Download (34.2 kB)

1
#
2
#
3

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

    
21

    
22
"""Cluster related QA tests.
23

24
"""
25

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

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

    
35
import qa_config
36
import qa_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
  # interaction with --drbd-usermode-helper option
459
  drbd_usermode_helper = qa_config.get("drbd-usermode-helper", None)
460
  if not drbd_usermode_helper:
461
    drbd_usermode_helper = "/bin/true"
462
  # specifying a helper when drbd gets disabled
463
  AssertCommand(["gnt-cluster", "modify",
464
                 "--drbd-usermode-helper=%s" % drbd_usermode_helper,
465
                 "--enabled-disk-templates=%s" % constants.DT_DISKLESS],
466
                 fail=False)
467
  if constants.DT_DRBD8 in enabled_disk_templates:
468
    # specifying a vg name when lvm is enabled
469
    AssertCommand(["gnt-cluster", "modify",
470
                   "--drbd-usermode-helper=%s" % drbd_usermode_helper,
471
                   "--enabled-disk-templates=%s" %
472
                     ",".join(enabled_disk_templates)],
473
                  fail=False)
474

    
475

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
555
  _RestoreEnabledDiskTemplates()
556

    
557

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

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

    
578

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

    
594

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

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

    
630

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

634
  @rtype: tuple
635
  @return: (policy, specs), where:
636
      - policy is a dictionary of the policy values, instance specs excluded
637
      - specs is dict of dict, specs[par][key] is a spec value, where key is
638
        "min", "max", or "std"
639

640
  """
641
  info = qa_utils.GetObjectInfo(["gnt-cluster", "info"])
642
  policy = info["Instance policy - limits for instances"]
643
  ret_specs = {}
644
  ret_policy = {}
645
  ispec_keys = constants.ISPECS_MINMAX_KEYS | frozenset([constants.ISPECS_STD])
646
  for (key, val) in policy.items():
647
    if key in ispec_keys:
648
      for (par, pval) in val.items():
649
        if par == "memory-size":
650
          par = "mem-size"
651
        d = ret_specs.setdefault(par, {})
652
        d[key] = pval
653
    else:
654
      ret_policy[key] = val
655

    
656
  # Sanity checks
657
  assert len(ret_specs) > 0
658
  good = all("min" in d and "std" in d and "max" in d
659
             for d in ret_specs.values())
660
  assert good, "Missing item in specs: %s" % ret_specs
661
  assert len(ret_policy) > 0
662
  return (ret_policy, ret_specs)
663

    
664

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

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

    
721

    
722
def TestClusterSetISpecs(new_specs, fail=False, old_values=None):
723
  """Change instance specs.
724

725
  @type new_specs: dict of dict
726
  @param new_specs: new_specs[par][key], where key is "min", "max", "std". It
727
      can be an empty dictionary.
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
  if old_values:
737
    (old_policy, old_specs) = old_values
738
  else:
739
    (old_policy, old_specs) = _GetClusterIPolicy()
740
  if new_specs:
741
    cmd = ["gnt-cluster", "modify"]
742
    for (par, keyvals) in new_specs.items():
743
      if par == "spindle-use":
744
        # ignore spindle-use, which is not settable
745
        continue
746
      cmd += [
747
        "--specs-%s" % par,
748
        ",".join(["%s=%s" % (k, v) for (k, v) in keyvals.items()]),
749
        ]
750
    AssertCommand(cmd, fail=fail)
751
  # Check the new state
752
  (eff_policy, eff_specs) = _GetClusterIPolicy()
753
  AssertEqual(eff_policy, old_policy)
754
  if fail:
755
    AssertEqual(eff_specs, old_specs)
756
  else:
757
    for par in eff_specs:
758
      for key in eff_specs[par]:
759
        if par in new_specs and key in new_specs[par]:
760
          AssertEqual(int(eff_specs[par][key]), int(new_specs[par][key]))
761
        else:
762
          AssertEqual(int(eff_specs[par][key]), int(old_specs[par][key]))
763
  return (eff_policy, eff_specs)
764

    
765

    
766
def TestClusterModifyISpecs():
767
  """gnt-cluster modify --specs-*"""
768
  params = ["mem-size", "disk-size", "disk-count", "cpu-count", "nic-count"]
769
  (cur_policy, cur_specs) = _GetClusterIPolicy()
770
  for par in params:
771
    test_values = [
772
      (True, 0, 4, 12),
773
      (True, 4, 4, 12),
774
      (True, 4, 12, 12),
775
      (True, 4, 4, 4),
776
      (False, 4, 0, 12),
777
      (False, 4, 16, 12),
778
      (False, 4, 4, 0),
779
      (False, 12, 4, 4),
780
      (False, 12, 4, 0),
781
      (False, "a", 4, 12),
782
      (False, 0, "a", 12),
783
      (False, 0, 4, "a"),
784
      # This is to restore the old values
785
      (True,
786
       cur_specs[par]["min"], cur_specs[par]["std"], cur_specs[par]["max"])
787
      ]
788
    for (good, mn, st, mx) in test_values:
789
      new_vals = {par: {"min": str(mn), "std": str(st), "max": str(mx)}}
790
      cur_state = (cur_policy, cur_specs)
791
      # We update cur_specs, as we've copied the values to restore already
792
      (cur_policy, cur_specs) = TestClusterSetISpecs(new_vals, fail=not good,
793
                                                     old_values=cur_state)
794

    
795

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

    
800

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

    
805

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

    
810

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

    
815

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

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

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

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

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

    
844
    utils.GenerateSelfSignedSslCert(fh.name, validity=validity)
845

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

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

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

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

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

    
877

    
878
def TestClusterBurnin():
879
  """Burnin"""
880
  master = qa_config.GetMasterNode()
881

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

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

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

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

    
930
  finally:
931
    for inst in instances:
932
      inst.Release()
933

    
934

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

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

    
948

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

952
  """
953
  return qa_utils.MakeNodePath(node, pathutils.JOB_QUEUE_DRAIN_FILE)
954

    
955

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

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

    
962

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

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

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

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

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

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

    
992

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

    
997
  uniqueid = utils.NewUUID()
998

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

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

    
1014

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

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

    
1029

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

    
1034

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

    
1039

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

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

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

    
1060

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

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