Statistics
| Branch: | Tag: | Revision:

root / qa / qa_cluster.py @ c632d3a5

History | View | Annotate | Download (34.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Cluster related QA tests.
23

24
"""
25

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

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

    
35
import qa_config
36
import qa_utils
37
import qa_error
38
import qa_instance
39

    
40
from qa_utils import AssertEqual, AssertCommand, GetCommandOutput
41

    
42

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

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

    
49

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

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

    
57

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

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

    
66

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

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

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

    
84

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

    
88

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

    
103

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

    
111

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

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

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

    
138

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

    
145

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

    
152

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

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

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

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

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

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

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

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

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

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

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

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

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

    
226
  AssertCommand(cmd)
227

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

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

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

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

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

    
253

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

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

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

    
272

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

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

    
282
  AssertCommand(_CLUSTER_VERIFY, fail=True)
283

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

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

    
292
    AssertCommand(_CLUSTER_VERIFY, fail=True)
293

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

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

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

    
304

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

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

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

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

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

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

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

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

    
343

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

    
349

    
350
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 a dictionary containing only the specs, using the internal
638
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
639

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

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

    
651

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

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

    
708

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

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

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

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

    
736

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

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

    
791

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

    
796

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

    
801

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

    
806

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

    
811

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

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

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

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

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

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

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

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

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

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

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

    
873

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

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

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

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

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

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

    
930

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

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

    
944

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

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

    
951

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

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

    
958

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

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

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

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

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

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

    
988

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

    
993
  uniqueid = utils.NewUUID()
994

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

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

    
1010

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

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

    
1025

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

    
1030

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

    
1035

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

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

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

    
1056

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

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