burnin 26.9 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
#!/usr/bin/python
#

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


"""Burnin program

"""
25

26
import os
Iustin Pop's avatar
Iustin Pop committed
27
28
import sys
import optparse
Iustin Pop's avatar
Iustin Pop committed
29
import time
30
31
32
import socket
import urllib2
import errno
33
from itertools import izip, islice, cycle
Iustin Pop's avatar
Iustin Pop committed
34
from cStringIO import StringIO
Iustin Pop's avatar
Iustin Pop committed
35
36
37
38
39

from ganeti import opcodes
from ganeti import mcpu
from ganeti import constants
from ganeti import cli
40
41
from ganeti import errors
from ganeti import utils
Iustin Pop's avatar
Iustin Pop committed
42

43

44
USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
Iustin Pop's avatar
Iustin Pop committed
45

46

47
48
49
50
class InstanceDown(Exception):
  """The checked instance was not up"""


Iustin Pop's avatar
Iustin Pop committed
51
52
53
54
55
56
57
def Usage():
  """Shows program usage information and exits the program."""

  print >> sys.stderr, "Usage:"
  print >> sys.stderr, USAGE
  sys.exit(2)

58

59
def Log(msg, indent=0):
60
61
62
  """Simple function that prints out its argument.

  """
63
64
65
66
67
68
69
  headers = {
    0: "- ",
    1: "* ",
    2: ""
    }
  sys.stdout.write("%*s%s%s\n" % (2*indent, "",
                                   headers.get(indent, "  "), msg))
70
  sys.stdout.flush()
Iustin Pop's avatar
Iustin Pop committed
71

72
73
74
75
76
77
78
def Err(msg, exit_code=1):
  """Simple error logging that prints to stderr.

  """
  sys.stderr.write(msg + "\n")
  sys.stderr.flush()
  sys.exit(exit_code)
79

80
81
82
83
84
class Burner(object):
  """Burner class."""

  def __init__(self):
    """Constructor."""
Iustin Pop's avatar
Iustin Pop committed
85
    utils.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True)
Iustin Pop's avatar
Iustin Pop committed
86
    self._feed_buf = StringIO()
87
88
89
90
91
    self.nodes = []
    self.instances = []
    self.to_rem = []
    self.opts = None
    self.ParseOptions()
92
    self.cl = cli.GetClient()
93
94
    self.GetState()

Iustin Pop's avatar
Iustin Pop committed
95
96
97
98
99
100
101
102
103
104
  def ClearFeedbackBuf(self):
    """Clear the feedback buffer."""
    self._feed_buf.truncate(0)

  def GetFeedbackBuf(self):
    """Return the contents of the buffer."""
    return self._feed_buf.getvalue()

  def Feedback(self, msg):
    """Acumulate feedback in our buffer."""
Iustin Pop's avatar
Iustin Pop committed
105
106
    self._feed_buf.write("%s %s\n" % (time.ctime(utils.MergeTime(msg[0])),
                                      msg[2]))
Iustin Pop's avatar
Iustin Pop committed
107
    if self.opts.verbose:
108
      Log(msg, indent=3)
Iustin Pop's avatar
Iustin Pop committed
109
110
111
112

  def ExecOp(self, op):
    """Execute an opcode and manage the exec buffer."""
    self.ClearFeedbackBuf()
113
114
115
116
117
118
119
120
121
122
123
124
    return cli.SubmitOpCode(op, feedback_fn=self.Feedback, cl=self.cl)

  def ExecJobSet(self, jobs):
    """Execute a set of jobs and return once all are done.

    The method will return the list of results, if all jobs are
    successfull. Otherwise, OpExecError will be raised from within
    cli.py.

    """
    self.ClearFeedbackBuf()
    job_ids = [cli.SendJob(job, cl=self.cl) for job in jobs]
125
    Log("Submitted job IDs %s" % ", ".join(job_ids), indent=1)
126
127
    results = []
    for jid in job_ids:
128
      Log("Waiting for job %s" % jid, indent=2)
129
130
131
      results.append(cli.PollJob(jid, cl=self.cl, feedback_fn=self.Feedback))

    return results
Iustin Pop's avatar
Iustin Pop committed
132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
  def ParseOptions(self):
    """Parses the command line options.

    In case of command line errors, it will show the usage and exit the
    program.

    """

    parser = optparse.OptionParser(usage="\n%s" % USAGE,
                                   version="%%prog (ganeti) %s" %
                                   constants.RELEASE_VERSION,
                                   option_class=cli.CliOption)

    parser.add_option("-o", "--os", dest="os", default=None,
                      help="OS to use during burnin",
                      metavar="<OS>")
149
150
151
152
    parser.add_option("--disk-size", dest="disk_size",
                      help="Disk size (determines disk count)",
                      default="128m", type="string", metavar="<size,size,...>")
    parser.add_option("--disk-growth", dest="disk_growth", help="Disk growth",
153
                      default="128m", type="string", metavar="<size,size,...>")
154
155
    parser.add_option("--mem-size", dest="mem_size", help="Memory size",
                      default=128, type="unit", metavar="<size>")
156
157
158
159
160
161
162
163
164
165
166
167
    parser.add_option("-v", "--verbose",
                      action="store_true", dest="verbose", default=False,
                      help="print command execution messages to stdout")
    parser.add_option("--no-replace1", dest="do_replace1",
                      help="Skip disk replacement with the same secondary",
                      action="store_false", default=True)
    parser.add_option("--no-replace2", dest="do_replace2",
                      help="Skip disk replacement with a different secondary",
                      action="store_false", default=True)
    parser.add_option("--no-failover", dest="do_failover",
                      help="Skip instance failovers", action="store_false",
                      default=True)
168
169
170
    parser.add_option("--no-migrate", dest="do_migrate",
                      help="Skip instance live migration",
                      action="store_false", default=True)
171
172
173
    parser.add_option("--no-importexport", dest="do_importexport",
                      help="Skip instance export/import", action="store_false",
                      default=True)
174
175
176
    parser.add_option("--no-startstop", dest="do_startstop",
                      help="Skip instance stop/start", action="store_false",
                      default=True)
177
178
179
180
181
182
    parser.add_option("--no-reinstall", dest="do_reinstall",
                      help="Skip instance reinstall", action="store_false",
                      default=True)
    parser.add_option("--no-reboot", dest="do_reboot",
                      help="Skip instance reboot", action="store_false",
                      default=True)
183
184
185
    parser.add_option("--no-activate-disks", dest="do_activate_disks",
                      help="Skip disk activation/deactivation",
                      action="store_false", default=True)
186
187
188
189
190
191
    parser.add_option("--no-add-disks", dest="do_addremove_disks",
                      help="Skip disk addition/removal",
                      action="store_false", default=True)
    parser.add_option("--no-add-nics", dest="do_addremove_nics",
                      help="Skip NIC addition/removal",
                      action="store_false", default=True)
192
193
194
    parser.add_option("--no-nics", dest="nics",
                      help="No network interfaces", action="store_const",
                      const=[], default=[{}])
195
196
197
198
    parser.add_option("--rename", dest="rename", default=None,
                      help="Give one unused instance name which is taken"
                           " to start the renaming sequence",
                      metavar="<instance_name>")
199
    parser.add_option("-t", "--disk-template", dest="disk_template",
200
                      choices=("diskless", "file", "plain", "drbd"),
201
                      default="drbd",
202
203
                      help="Disk template (diskless, file, plain or drbd)"
                            " [drbd]")
204
205
206
    parser.add_option("-n", "--nodes", dest="nodes", default="",
                      help="Comma separated list of nodes to perform"
                      " the burnin on (defaults to all nodes)")
207
208
209
210
211
    parser.add_option("--iallocator", dest="iallocator",
                      default=None, type="string",
                      help="Perform the allocation using an iallocator"
                      " instead of fixed node spread (node restrictions no"
                      " longer apply, therefore -n/--nodes must not be used")
212
213
214
215
    parser.add_option("-p", "--parallel", default=False, action="store_true",
                      dest="parallel",
                      help="Enable parallelization of some operations in"
                      " order to speed burnin or to test granular locking")
216
217
218
219
220
221
222
223
224
    parser.add_option("--net-timeout", default=15, type="int",
                      dest="net_timeout",
                      help="The instance check network timeout in seconds"
                      " (defaults to 15 seconds)")
    parser.add_option("-C", "--http-check", default=False, action="store_true",
                      dest="http_check",
                      help="Enable checking of instance status via http,"
                      " looking for /hostname.txt that should contain the"
                      " name of the instance")
225
226
227
228
229
230
    parser.add_option("-K", "--keep-instances", default=False,
                      action="store_true",
                      dest="keep_instances",
                      help="Leave instances on the cluster after burnin,"
                      " for investigation in case of errors or simply"
                      " to use them")
231

232
233
234
235
236

    options, args = parser.parse_args()
    if len(args) < 1 or options.os is None:
      Usage()

237
    supported_disk_templates = (constants.DT_DISKLESS,
238
                                constants.DT_FILE,
239
                                constants.DT_PLAIN,
240
241
                                constants.DT_DRBD8)
    if options.disk_template not in supported_disk_templates:
242
      Err("Unknown disk template '%s'" % options.disk_template)
243

244
245
    if options.disk_template == constants.DT_DISKLESS:
      disk_size = disk_growth = []
246
      options.do_addremove_disks = False
247
248
249
250
251
    else:
      disk_size = [utils.ParseUnit(v) for v in options.disk_size.split(",")]
      disk_growth = [utils.ParseUnit(v)
                     for v in options.disk_growth.split(",")]
      if len(disk_growth) != len(disk_size):
252
        Err("Wrong disk sizes/growth combination")
253
254
    if ((disk_size and options.disk_template == constants.DT_DISKLESS) or
        (not disk_size and options.disk_template != constants.DT_DISKLESS)):
255
      Err("Wrong disk count/disk template combination")
256
257
258
259
260

    self.disk_size = disk_size
    self.disk_growth = disk_growth
    self.disk_count = len(disk_size)

261
    if options.nodes and options.iallocator:
262
      Err("Give either the nodes option or the iallocator option, not both")
263

264
265
    self.opts = options
    self.instances = args
Iustin Pop's avatar
Iustin Pop committed
266
267
268
269
270
    self.bep = {
      constants.BE_MEMORY: options.mem_size,
      constants.BE_VCPUS: 1,
      }
    self.hvp = {}
271

272
273
    socket.setdefaulttimeout(options.net_timeout)

274
275
276
277
278
279
280
  def GetState(self):
    """Read the cluster state from the config."""
    if self.opts.nodes:
      names = self.opts.nodes.split(",")
    else:
      names = []
    try:
Iustin Pop's avatar
Iustin Pop committed
281
      op = opcodes.OpQueryNodes(output_fields=["name", "offline"], names=names)
Iustin Pop's avatar
Iustin Pop committed
282
      result = self.ExecOp(op)
283
284
    except errors.GenericError, err:
      err_code, msg = cli.FormatError(err)
285
      Err(msg, exit_code=err_code)
Iustin Pop's avatar
Iustin Pop committed
286
    self.nodes = [data[0] for data in result if not data[1]]
287

288
289
    result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
                                              names=[]))
290
291

    if not result:
292
      Err("Can't get the OS list")
293
294

    # filter non-valid OS-es
295
    os_set = [val[0] for val in result if val[1]]
296
297

    if self.opts.os not in os_set:
298
      Err("OS '%s' not found" % self.opts.os)
299
300
301
302
303
304
305
306
307

  def CreateInstances(self):
    """Create the given instances.

    """
    self.to_rem = []
    mytor = izip(cycle(self.nodes),
                 islice(cycle(self.nodes), 1, None),
                 self.instances)
308
    jobset = []
Iustin Pop's avatar
Iustin Pop committed
309

310
    Log("Creating instances")
311
    for pnode, snode, instance in mytor:
312
      Log("instance %s" % instance, indent=1)
313
314
      if self.opts.iallocator:
        pnode = snode = None
315
        msg = "with iallocator %s" % self.opts.iallocator
316
317
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
        snode = None
318
        msg = "on %s" % pnode
319
      else:
320
321
322
        msg = "on %s, %s" % (pnode, snode)

      Log(msg, indent=2)
323

324
      op = opcodes.OpCreateInstance(instance_name=instance,
325
326
                                    disks = [ {"size": size}
                                              for size in self.disk_size],
327
                                    disk_template=self.opts.disk_template,
328
                                    nics=self.opts.nics,
Iustin Pop's avatar
Iustin Pop committed
329
                                    mode=constants.INSTANCE_CREATE,
330
331
332
                                    os_type=self.opts.os,
                                    pnode=pnode,
                                    snode=snode,
Iustin Pop's avatar
Iustin Pop committed
333
                                    start=True,
334
                                    ip_check=True,
335
                                    wait_for_sync=True,
336
                                    file_driver="loop",
337
                                    file_storage_dir=None,
338
                                    iallocator=self.opts.iallocator,
Iustin Pop's avatar
Iustin Pop committed
339
340
341
                                    beparams=self.bep,
                                    hvparams=self.hvp,
                                    )
342

343
344
345
346
347
348
349
350
351
352
      if self.opts.parallel:
        jobset.append([op])
        # FIXME: here we should not append to to_rem uncoditionally,
        # but only when the job is successful
        self.to_rem.append(instance)
      else:
        self.ExecOp(op)
        self.to_rem.append(instance)
    if self.opts.parallel:
      self.ExecJobSet(jobset)
353

354
355
356
    for instance in self.instances:
      self._CheckInstanceAlive(instance)

Iustin Pop's avatar
Iustin Pop committed
357
358
  def GrowDisks(self):
    """Grow both the os and the swap disks by the requested amount, if any."""
359
    Log("Growing disks")
Iustin Pop's avatar
Iustin Pop committed
360
    for instance in self.instances:
361
      Log("instance %s" % instance, indent=1)
362
      for idx, growth in enumerate(self.disk_growth):
Iustin Pop's avatar
Iustin Pop committed
363
        if growth > 0:
364
          op = opcodes.OpGrowDisk(instance_name=instance, disk=idx,
365
                                  amount=growth, wait_for_sync=True)
366
          Log("increase disk/%s by %s MB" % (idx, growth), indent=2)
Iustin Pop's avatar
Iustin Pop committed
367
368
          self.ExecOp(op)

369
370
  def ReplaceDisks1D8(self):
    """Replace disks on primary and secondary for drbd8."""
371
    Log("Replacing disks on the same nodes")
372
    for instance in self.instances:
373
      Log("instance %s" % instance, indent=1)
374
375
376
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
        op = opcodes.OpReplaceDisks(instance_name=instance,
                                    mode=mode,
377
                                    disks=[i for i in range(self.disk_count)])
378
        Log("run %s" % mode, indent=2)
Iustin Pop's avatar
Iustin Pop committed
379
        self.ExecOp(op)
380
381
382

  def ReplaceDisks2(self):
    """Replace secondary node."""
383
    Log("Changing the secondary node")
384
    mode = constants.REPLACE_DISK_CHG
385
386
387
388

    mytor = izip(islice(cycle(self.nodes), 2, None),
                 self.instances)
    for tnode, instance in mytor:
389
      Log("instance %s" % instance, indent=1)
390
391
      if self.opts.iallocator:
        tnode = None
392
393
394
        msg = "with iallocator %s" % self.opts.iallocator
      else:
        msg = tnode
395
396
397
      op = opcodes.OpReplaceDisks(instance_name=instance,
                                  mode=mode,
                                  remote_node=tnode,
398
                                  iallocator=self.opts.iallocator,
399
                                  disks=[i for i in range(self.disk_count)])
400
      Log("run %s %s" % (mode, msg), indent=2)
Iustin Pop's avatar
Iustin Pop committed
401
      self.ExecOp(op)
402
403
404

  def Failover(self):
    """Failover the instances."""
405
    Log("Failing over instances")
406
    for instance in self.instances:
407
      Log("instance %s" % instance, indent=1)
408
409
410
      op = opcodes.OpFailoverInstance(instance_name=instance,
                                      ignore_consistency=False)

Iustin Pop's avatar
Iustin Pop committed
411
      self.ExecOp(op)
412
413
    for instance in self.instances:
      self._CheckInstanceAlive(instance)
414

415
416
  def Migrate(self):
    """Migrate the instances."""
417
    Log("Migrating instances")
418
    for instance in self.instances:
419
      Log("instance %s" % instance, indent=1)
420
421
422
      op = opcodes.OpMigrateInstance(instance_name=instance, live=True,
                                     cleanup=False)

423
      Log("migration", indent=2)
424
425
426
      self.ExecOp(op)
      op = opcodes.OpMigrateInstance(instance_name=instance, live=True,
                                     cleanup=True)
427
      Log("migration cleanup", indent=2)
428
429
      self.ExecOp(op)

430
431
432
433
  def ImportExport(self):
    """Export the instance, delete it, and import it back.

    """
434
    Log("Exporting and re-importing instances")
435
436
437
438
439
440
    mytor = izip(cycle(self.nodes),
                 islice(cycle(self.nodes), 1, None),
                 islice(cycle(self.nodes), 2, None),
                 self.instances)

    for pnode, snode, enode, instance in mytor:
441
      Log("instance %s" % instance, indent=1)
442
443
      if self.opts.iallocator:
        pnode = snode = None
444
445
446
        import_log_msg = ("import from %s"
                          " with iallocator %s" %
                          (enode, self.opts.iallocator))
447
448
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
        snode = None
449
450
        import_log_msg = ("import from %s to %s" %
                          (enode, pnode))
451
      else:
452
453
        import_log_msg = ("import from %s to %s, %s" %
                          (enode, pnode, snode))
454

455
456
457
      exp_op = opcodes.OpExportInstance(instance_name=instance,
                                           target_node=enode,
                                           shutdown=True)
458
459
      rem_op = opcodes.OpRemoveInstance(instance_name=instance,
                                        ignore_failures=True)
460
461
462
463
464
      nam_op = opcodes.OpQueryInstances(output_fields=["name"],
                                           names=[instance])
      full_name = self.ExecOp(nam_op)[0][0]
      imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
      imp_op = opcodes.OpCreateInstance(instance_name=instance,
Guido Trotter's avatar
Guido Trotter committed
465
466
                                        disks = [ {"size": size}
                                                  for size in self.disk_size],
467
                                        disk_template=self.opts.disk_template,
468
                                        nics=self.opts.nics,
469
470
471
472
473
474
475
476
                                        mode=constants.INSTANCE_IMPORT,
                                        src_node=enode,
                                        src_path=imp_dir,
                                        pnode=pnode,
                                        snode=snode,
                                        start=True,
                                        ip_check=True,
                                        wait_for_sync=True,
477
                                        file_storage_dir=None,
Guido Trotter's avatar
Guido Trotter committed
478
                                        file_driver="loop",
479
                                        iallocator=self.opts.iallocator,
Iustin Pop's avatar
Iustin Pop committed
480
481
482
                                        beparams=self.bep,
                                        hvparams=self.hvp,
                                        )
483

Guido Trotter's avatar
Guido Trotter committed
484
      erem_op = opcodes.OpRemoveExport(instance_name=instance)
485

486
      Log("export to node %s" % enode, indent=2)
487
      self.ExecOp(exp_op)
488
      Log("remove instance", indent=2)
489
490
      self.ExecOp(rem_op)
      self.to_rem.remove(instance)
491
      Log(import_log_msg, indent=2)
492
      self.ExecOp(imp_op)
493
      Log("remove export", indent=2)
Guido Trotter's avatar
Guido Trotter committed
494
495
      self.ExecOp(erem_op)

496
497
      self.to_rem.append(instance)

498
499
500
    for instance in self.instances:
      self._CheckInstanceAlive(instance)

501
502
503
  def StopInstance(self, instance):
    """Stop given instance."""
    op = opcodes.OpShutdownInstance(instance_name=instance)
504
    Log("shutdown", indent=2)
505
506
507
508
509
    self.ExecOp(op)

  def StartInstance(self, instance):
    """Start given instance."""
    op = opcodes.OpStartupInstance(instance_name=instance, force=False)
510
    Log("startup", indent=2)
511
512
513
514
515
516
    self.ExecOp(op)

  def RenameInstance(self, instance, instance_new):
    """Rename instance."""
    op = opcodes.OpRenameInstance(instance_name=instance,
                                  new_name=instance_new)
517
    Log("rename to %s" % instance_new, indent=2)
518
519
    self.ExecOp(op)

520
521
  def StopStart(self):
    """Stop/start the instances."""
522
    Log("Stopping and starting instances")
523
    for instance in self.instances:
524
      Log("instance %s" % instance, indent=1)
525
526
      self.StopInstance(instance)
      self.StartInstance(instance)
527

528
529
530
    for instance in self.instances:
      self._CheckInstanceAlive(instance)

531
532
  def Remove(self):
    """Remove the instances."""
533
    Log("Removing instances")
534
    for instance in self.to_rem:
535
      Log("instance %s" % instance, indent=1)
536
537
      op = opcodes.OpRemoveInstance(instance_name=instance,
                                    ignore_failures=True)
Iustin Pop's avatar
Iustin Pop committed
538
      self.ExecOp(op)
539

540
541
  def Rename(self):
    """Rename the instances."""
542
    Log("Renaming instances")
543
544
    rename = self.opts.rename
    for instance in self.instances:
545
      Log("instance %s" % instance, indent=1)
546
547
548
      self.StopInstance(instance)
      self.RenameInstance(instance, rename)
      self.StartInstance(rename)
549
      self._CheckInstanceAlive(rename)
550
551
552
553
      self.StopInstance(rename)
      self.RenameInstance(rename, instance)
      self.StartInstance(instance)

554
555
556
    for instance in self.instances:
      self._CheckInstanceAlive(instance)

557
558
  def Reinstall(self):
    """Reinstall the instances."""
559
    Log("Reinstalling instances")
560
    for instance in self.instances:
561
      Log("instance %s" % instance, indent=1)
562
563
      self.StopInstance(instance)
      op = opcodes.OpReinstallInstance(instance_name=instance)
564
      Log("reinstall without passing the OS", indent=2)
565
566
567
      self.ExecOp(op)
      op = opcodes.OpReinstallInstance(instance_name=instance,
                                       os_type=self.opts.os)
568
      Log("reinstall specifying the OS", indent=2)
569
570
      self.ExecOp(op)
      self.StartInstance(instance)
571
572
    for instance in self.instances:
      self._CheckInstanceAlive(instance)
573
574

  def Reboot(self):
575
576
    """Reboot the instances."""
    Log("Rebooting instances")
577
    for instance in self.instances:
578
      Log("instance %s" % instance, indent=1)
579
580
581
582
      for reboot_type in constants.REBOOT_TYPES:
        op = opcodes.OpRebootInstance(instance_name=instance,
                                      reboot_type=reboot_type,
                                      ignore_secondaries=False)
583
        Log("reboot with type '%s'" % reboot_type, indent=2)
584
        self.ExecOp(op)
585
        self._CheckInstanceAlive(instance)
586

587
588
  def ActivateDisks(self):
    """Activate and deactivate disks of the instances."""
589
    Log("Activating/deactivating disks")
590
    for instance in self.instances:
591
      Log("instance %s" % instance, indent=1)
592
593
      op_act = opcodes.OpActivateInstanceDisks(instance_name=instance)
      op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance)
594
      Log("activate disks when online", indent=2)
595
596
      self.ExecOp(op_act)
      self.StopInstance(instance)
597
      Log("activate disks when offline", indent=2)
598
      self.ExecOp(op_act)
599
      Log("deactivate disks (when offline)", indent=2)
600
601
      self.ExecOp(op_deact)
      self.StartInstance(instance)
602
603
    for instance in self.instances:
      self._CheckInstanceAlive(instance)
604

605
606
  def AddRemoveDisks(self):
    """Add and remove an extra disk for the instances."""
607
    Log("Adding and removing disks")
608
    for instance in self.instances:
609
      Log("instance %s" % instance, indent=1)
610
611
612
613
614
      op_add = opcodes.OpSetInstanceParams(\
        instance_name=instance,
        disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})])
      op_rem = opcodes.OpSetInstanceParams(\
        instance_name=instance, disks=[(constants.DDM_REMOVE, {})])
615
      Log("adding a disk", indent=2)
616
617
      self.ExecOp(op_add)
      self.StopInstance(instance)
618
      Log("removing last disk", indent=2)
619
620
      self.ExecOp(op_rem)
      self.StartInstance(instance)
621
622
    for instance in self.instances:
      self._CheckInstanceAlive(instance)
623
624
625

  def AddRemoveNICs(self):
    """Add and remove an extra NIC for the instances."""
626
    Log("Adding and removing NICs")
627
    for instance in self.instances:
628
      Log("instance %s" % instance, indent=1)
629
630
631
632
      op_add = opcodes.OpSetInstanceParams(\
        instance_name=instance, nics=[(constants.DDM_ADD, {})])
      op_rem = opcodes.OpSetInstanceParams(\
        instance_name=instance, nics=[(constants.DDM_REMOVE, {})])
633
      Log("adding a NIC", indent=2)
634
      self.ExecOp(op_add)
635
      Log("removing last NIC", indent=2)
636
637
      self.ExecOp(op_rem)

638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
  def _CheckInstanceAlive(self, instance):
    """Check if an instance is alive by doing http checks.

    This will try to retrieve the url on the instance /hostname.txt
    and check that it contains the hostname of the instance. In case
    we get ECONNREFUSED, we retry up to the net timeout seconds, for
    any other error we abort.

    """
    if not self.opts.http_check:
      return
    try:
      for retries in range(self.opts.net_timeout):
        try:
          url = urllib2.urlopen("http://%s/hostname.txt" % instance)
        except urllib2.URLError, err:
          if err.args[0][0] == errno.ECONNREFUSED:
            time.sleep(1)
            continue
          raise
    except urllib2.URLError, err:
      raise InstanceDown(instance, str(err))
    hostname = url.read().strip()
    if hostname != instance:
      raise InstanceDown(instance, ("Hostname mismatch, expected %s, got %s" %
                                    (instance, hostname)))

665
666
667
668
669
670
671
672
673
674
  def BurninCluster(self):
    """Test a cluster intensively.

    This will create instances and then start/stop/failover them.
    It is safe for existing instances but could impact performance.

    """

    opts = self.opts

675
    Log("Testing global parameters")
676

677
    if (len(self.nodes) == 1 and
678
679
        opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
                                   constants.DT_FILE)):
680
      Err("When one node is available/selected the disk template must"
681
          " be 'diskless', 'file' or 'plain'")
682

Iustin Pop's avatar
Iustin Pop committed
683
    has_err = True
684
685
686
    try:
      self.CreateInstances()
      if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
687
        self.ReplaceDisks1D8()
688
689
690
691
      if (opts.do_replace2 and len(self.nodes) > 2 and
          opts.disk_template in constants.DTS_NET_MIRROR) :
        self.ReplaceDisks2()

692
693
      if (opts.disk_template != constants.DT_DISKLESS and
          utils.any(self.disk_growth, lambda n: n > 0)):
Iustin Pop's avatar
Iustin Pop committed
694
695
        self.GrowDisks()

696
697
698
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
        self.Failover()

699
700
701
      if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
        self.Migrate()

702
703
704
      if (opts.do_importexport and
          opts.disk_template not in (constants.DT_DISKLESS,
                                     constants.DT_FILE)):
705
706
        self.ImportExport()

707
708
709
710
711
712
      if opts.do_reinstall:
        self.Reinstall()

      if opts.do_reboot:
        self.Reboot()

713
714
715
716
717
718
      if opts.do_addremove_disks:
        self.AddRemoveDisks()

      if opts.do_addremove_nics:
        self.AddRemoveNICs()

719
720
721
      if opts.do_activate_disks:
        self.ActivateDisks()

722
723
724
      if opts.rename:
        self.Rename()

725
726
727
      if opts.do_startstop:
        self.StopStart()

Iustin Pop's avatar
Iustin Pop committed
728
      has_err = False
729
    finally:
Iustin Pop's avatar
Iustin Pop committed
730
731
732
733
      if has_err:
        Log("Error detected: opcode buffer follows:\n\n")
        Log(self.GetFeedbackBuf())
        Log("\n\n")
734
735
      if not self.opts.keep_instances:
        self.Remove()
736
737

    return 0
Iustin Pop's avatar
Iustin Pop committed
738

739

Iustin Pop's avatar
Iustin Pop committed
740
def main():
741
742
  """Main function"""

743
  burner = Burner()
744
  return burner.BurninCluster()
Iustin Pop's avatar
Iustin Pop committed
745

746

Iustin Pop's avatar
Iustin Pop committed
747
if __name__ == "__main__":
748
  main()