Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ 5dc626fd

History | View | Annotate | Download (27.6 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 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
"""Burnin program
23

    
24
"""
25

    
26
import os
27
import sys
28
import optparse
29
import time
30
import socket
31
import urllib
32
import errno
33
from itertools import izip, islice, cycle
34
from cStringIO import StringIO
35

    
36
from ganeti import opcodes
37
from ganeti import mcpu
38
from ganeti import constants
39
from ganeti import cli
40
from ganeti import errors
41
from ganeti import utils
42

    
43

    
44
USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
45

    
46

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

    
50

    
51
def Usage():
52
  """Shows program usage information and exits the program."""
53

    
54
  print >> sys.stderr, "Usage:"
55
  print >> sys.stderr, USAGE
56
  sys.exit(2)
57

    
58

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

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

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

    
75
  """
76
  sys.stderr.write(msg + "\n")
77
  sys.stderr.flush()
78
  sys.exit(exit_code)
79

    
80

    
81
class SimpleOpener(urllib.FancyURLopener):
82
  """A simple url opener"""
83

    
84
  def prompt_user_passwd(self, host, realm, clear_cache = 0):
85
    """No-interaction version of prompt_user_passwd."""
86
    return None, None
87

    
88
  def http_error_default(self, url, fp, errcode, errmsg, headers):
89
    """Custom error handling"""
90
    # make sure sockets are not left in CLOSE_WAIT, this is similar
91
    # but with a different exception to the BasicURLOpener class
92
    _ = fp.read() # throw away data
93
    fp.close()
94
    raise InstanceDown("HTTP error returned: code %s, msg %s" %
95
                       (errcode, errmsg))
96

    
97

    
98
class Burner(object):
99
  """Burner class."""
100

    
101
  def __init__(self):
102
    """Constructor."""
103
    utils.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True)
104
    self.url_opener = SimpleOpener()
105
    self._feed_buf = StringIO()
106
    self.nodes = []
107
    self.instances = []
108
    self.to_rem = []
109
    self.opts = None
110
    self.ParseOptions()
111
    self.cl = cli.GetClient()
112
    self.GetState()
113

    
114
  def ClearFeedbackBuf(self):
115
    """Clear the feedback buffer."""
116
    self._feed_buf.truncate(0)
117

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

    
122
  def Feedback(self, msg):
123
    """Acumulate feedback in our buffer."""
124
    self._feed_buf.write("%s %s\n" % (time.ctime(utils.MergeTime(msg[0])),
125
                                      msg[2]))
126
    if self.opts.verbose:
127
      Log(msg, indent=3)
128

    
129
  def ExecOp(self, op):
130
    """Execute an opcode and manage the exec buffer."""
131
    self.ClearFeedbackBuf()
132
    return cli.SubmitOpCode(op, feedback_fn=self.Feedback, cl=self.cl)
133

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

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

    
141
    """
142
    self.ClearFeedbackBuf()
143
    job_ids = [cli.SendJob(job, cl=self.cl) for job in jobs]
144
    Log("Submitted job IDs %s" % ", ".join(job_ids), indent=1)
145
    results = []
146
    for jid in job_ids:
147
      Log("Waiting for job %s" % jid, indent=2)
148
      results.append(cli.PollJob(jid, cl=self.cl, feedback_fn=self.Feedback))
149

    
150
    return results
151

    
152
  def ParseOptions(self):
153
    """Parses the command line options.
154

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

    
158
    """
159

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

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

    
251

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

    
256
    supported_disk_templates = (constants.DT_DISKLESS,
257
                                constants.DT_FILE,
258
                                constants.DT_PLAIN,
259
                                constants.DT_DRBD8)
260
    if options.disk_template not in supported_disk_templates:
261
      Err("Unknown disk template '%s'" % options.disk_template)
262

    
263
    if options.disk_template == constants.DT_DISKLESS:
264
      disk_size = disk_growth = []
265
      options.do_addremove_disks = False
266
    else:
267
      disk_size = [utils.ParseUnit(v) for v in options.disk_size.split(",")]
268
      disk_growth = [utils.ParseUnit(v)
269
                     for v in options.disk_growth.split(",")]
270
      if len(disk_growth) != len(disk_size):
271
        Err("Wrong disk sizes/growth combination")
272
    if ((disk_size and options.disk_template == constants.DT_DISKLESS) or
273
        (not disk_size and options.disk_template != constants.DT_DISKLESS)):
274
      Err("Wrong disk count/disk template combination")
275

    
276
    self.disk_size = disk_size
277
    self.disk_growth = disk_growth
278
    self.disk_count = len(disk_size)
279

    
280
    if options.nodes and options.iallocator:
281
      Err("Give either the nodes option or the iallocator option, not both")
282

    
283
    self.opts = options
284
    self.instances = args
285
    self.bep = {
286
      constants.BE_MEMORY: options.mem_size,
287
      constants.BE_VCPUS: 1,
288
      }
289
    self.hvp = {}
290

    
291
    socket.setdefaulttimeout(options.net_timeout)
292

    
293
  def GetState(self):
294
    """Read the cluster state from the config."""
295
    if self.opts.nodes:
296
      names = self.opts.nodes.split(",")
297
    else:
298
      names = []
299
    try:
300
      op = opcodes.OpQueryNodes(output_fields=["name", "offline"], names=names)
301
      result = self.ExecOp(op)
302
    except errors.GenericError, err:
303
      err_code, msg = cli.FormatError(err)
304
      Err(msg, exit_code=err_code)
305
    self.nodes = [data[0] for data in result if not data[1]]
306

    
307
    result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
308
                                              names=[]))
309

    
310
    if not result:
311
      Err("Can't get the OS list")
312

    
313
    # filter non-valid OS-es
314
    os_set = [val[0] for val in result if val[1]]
315

    
316
    if self.opts.os not in os_set:
317
      Err("OS '%s' not found" % self.opts.os)
318

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

    
322
    """
323
    self.to_rem = []
324
    mytor = izip(cycle(self.nodes),
325
                 islice(cycle(self.nodes), 1, None),
326
                 self.instances)
327
    jobset = []
328

    
329
    Log("Creating instances")
330
    for pnode, snode, instance in mytor:
331
      Log("instance %s" % instance, indent=1)
332
      if self.opts.iallocator:
333
        pnode = snode = None
334
        msg = "with iallocator %s" % self.opts.iallocator
335
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
336
        snode = None
337
        msg = "on %s" % pnode
338
      else:
339
        msg = "on %s, %s" % (pnode, snode)
340

    
341
      Log(msg, indent=2)
342

    
343
      op = opcodes.OpCreateInstance(instance_name=instance,
344
                                    disks = [ {"size": size}
345
                                              for size in self.disk_size],
346
                                    disk_template=self.opts.disk_template,
347
                                    nics=self.opts.nics,
348
                                    mode=constants.INSTANCE_CREATE,
349
                                    os_type=self.opts.os,
350
                                    pnode=pnode,
351
                                    snode=snode,
352
                                    start=True,
353
                                    ip_check=True,
354
                                    wait_for_sync=True,
355
                                    file_driver="loop",
356
                                    file_storage_dir=None,
357
                                    iallocator=self.opts.iallocator,
358
                                    beparams=self.bep,
359
                                    hvparams=self.hvp,
360
                                    )
361

    
362
      if self.opts.parallel:
363
        jobset.append([op])
364
        # FIXME: here we should not append to to_rem uncoditionally,
365
        # but only when the job is successful
366
        self.to_rem.append(instance)
367
      else:
368
        self.ExecOp(op)
369
        self.to_rem.append(instance)
370
    if self.opts.parallel:
371
      self.ExecJobSet(jobset)
372

    
373
    for instance in self.instances:
374
      self._CheckInstanceAlive(instance)
375

    
376
  def GrowDisks(self):
377
    """Grow both the os and the swap disks by the requested amount, if any."""
378
    Log("Growing disks")
379
    for instance in self.instances:
380
      Log("instance %s" % instance, indent=1)
381
      for idx, growth in enumerate(self.disk_growth):
382
        if growth > 0:
383
          op = opcodes.OpGrowDisk(instance_name=instance, disk=idx,
384
                                  amount=growth, wait_for_sync=True)
385
          Log("increase disk/%s by %s MB" % (idx, growth), indent=2)
386
          self.ExecOp(op)
387

    
388
  def ReplaceDisks1D8(self):
389
    """Replace disks on primary and secondary for drbd8."""
390
    Log("Replacing disks on the same nodes")
391
    for instance in self.instances:
392
      Log("instance %s" % instance, indent=1)
393
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
394
        op = opcodes.OpReplaceDisks(instance_name=instance,
395
                                    mode=mode,
396
                                    disks=[i for i in range(self.disk_count)])
397
        Log("run %s" % mode, indent=2)
398
        self.ExecOp(op)
399

    
400
  def ReplaceDisks2(self):
401
    """Replace secondary node."""
402
    Log("Changing the secondary node")
403
    mode = constants.REPLACE_DISK_CHG
404

    
405
    mytor = izip(islice(cycle(self.nodes), 2, None),
406
                 self.instances)
407
    for tnode, instance in mytor:
408
      Log("instance %s" % instance, indent=1)
409
      if self.opts.iallocator:
410
        tnode = None
411
        msg = "with iallocator %s" % self.opts.iallocator
412
      else:
413
        msg = tnode
414
      op = opcodes.OpReplaceDisks(instance_name=instance,
415
                                  mode=mode,
416
                                  remote_node=tnode,
417
                                  iallocator=self.opts.iallocator,
418
                                  disks=[i for i in range(self.disk_count)])
419
      Log("run %s %s" % (mode, msg), indent=2)
420
      self.ExecOp(op)
421

    
422
  def Failover(self):
423
    """Failover the instances."""
424
    Log("Failing over instances")
425
    for instance in self.instances:
426
      Log("instance %s" % instance, indent=1)
427
      op = opcodes.OpFailoverInstance(instance_name=instance,
428
                                      ignore_consistency=False)
429

    
430
      self.ExecOp(op)
431
    for instance in self.instances:
432
      self._CheckInstanceAlive(instance)
433

    
434
  def Migrate(self):
435
    """Migrate the instances."""
436
    Log("Migrating instances")
437
    for instance in self.instances:
438
      Log("instance %s" % instance, indent=1)
439
      op = opcodes.OpMigrateInstance(instance_name=instance, live=True,
440
                                     cleanup=False)
441

    
442
      Log("migration", indent=2)
443
      self.ExecOp(op)
444
      op = opcodes.OpMigrateInstance(instance_name=instance, live=True,
445
                                     cleanup=True)
446
      Log("migration cleanup", indent=2)
447
      self.ExecOp(op)
448

    
449
  def ImportExport(self):
450
    """Export the instance, delete it, and import it back.
451

    
452
    """
453
    Log("Exporting and re-importing instances")
454
    mytor = izip(cycle(self.nodes),
455
                 islice(cycle(self.nodes), 1, None),
456
                 islice(cycle(self.nodes), 2, None),
457
                 self.instances)
458

    
459
    for pnode, snode, enode, instance in mytor:
460
      Log("instance %s" % instance, indent=1)
461
      if self.opts.iallocator:
462
        pnode = snode = None
463
        import_log_msg = ("import from %s"
464
                          " with iallocator %s" %
465
                          (enode, self.opts.iallocator))
466
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
467
        snode = None
468
        import_log_msg = ("import from %s to %s" %
469
                          (enode, pnode))
470
      else:
471
        import_log_msg = ("import from %s to %s, %s" %
472
                          (enode, pnode, snode))
473

    
474
      exp_op = opcodes.OpExportInstance(instance_name=instance,
475
                                           target_node=enode,
476
                                           shutdown=True)
477
      rem_op = opcodes.OpRemoveInstance(instance_name=instance,
478
                                        ignore_failures=True)
479
      nam_op = opcodes.OpQueryInstances(output_fields=["name"],
480
                                           names=[instance])
481
      full_name = self.ExecOp(nam_op)[0][0]
482
      imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
483
      imp_op = opcodes.OpCreateInstance(instance_name=instance,
484
                                        disks = [ {"size": size}
485
                                                  for size in self.disk_size],
486
                                        disk_template=self.opts.disk_template,
487
                                        nics=self.opts.nics,
488
                                        mode=constants.INSTANCE_IMPORT,
489
                                        src_node=enode,
490
                                        src_path=imp_dir,
491
                                        pnode=pnode,
492
                                        snode=snode,
493
                                        start=True,
494
                                        ip_check=True,
495
                                        wait_for_sync=True,
496
                                        file_storage_dir=None,
497
                                        file_driver="loop",
498
                                        iallocator=self.opts.iallocator,
499
                                        beparams=self.bep,
500
                                        hvparams=self.hvp,
501
                                        )
502

    
503
      erem_op = opcodes.OpRemoveExport(instance_name=instance)
504

    
505
      Log("export to node %s" % enode, indent=2)
506
      self.ExecOp(exp_op)
507
      Log("remove instance", indent=2)
508
      self.ExecOp(rem_op)
509
      self.to_rem.remove(instance)
510
      Log(import_log_msg, indent=2)
511
      self.ExecOp(imp_op)
512
      Log("remove export", indent=2)
513
      self.ExecOp(erem_op)
514

    
515
      self.to_rem.append(instance)
516

    
517
    for instance in self.instances:
518
      self._CheckInstanceAlive(instance)
519

    
520
  def StopInstance(self, instance):
521
    """Stop given instance."""
522
    op = opcodes.OpShutdownInstance(instance_name=instance)
523
    Log("shutdown", indent=2)
524
    self.ExecOp(op)
525

    
526
  def StartInstance(self, instance):
527
    """Start given instance."""
528
    op = opcodes.OpStartupInstance(instance_name=instance, force=False)
529
    Log("startup", indent=2)
530
    self.ExecOp(op)
531

    
532
  def RenameInstance(self, instance, instance_new):
533
    """Rename instance."""
534
    op = opcodes.OpRenameInstance(instance_name=instance,
535
                                  new_name=instance_new)
536
    Log("rename to %s" % instance_new, indent=2)
537
    self.ExecOp(op)
538

    
539
  def StopStart(self):
540
    """Stop/start the instances."""
541
    Log("Stopping and starting instances")
542
    for instance in self.instances:
543
      Log("instance %s" % instance, indent=1)
544
      self.StopInstance(instance)
545
      self.StartInstance(instance)
546

    
547
    for instance in self.instances:
548
      self._CheckInstanceAlive(instance)
549

    
550
  def Remove(self):
551
    """Remove the instances."""
552
    Log("Removing instances")
553
    for instance in self.to_rem:
554
      Log("instance %s" % instance, indent=1)
555
      op = opcodes.OpRemoveInstance(instance_name=instance,
556
                                    ignore_failures=True)
557
      self.ExecOp(op)
558

    
559
  def Rename(self):
560
    """Rename the instances."""
561
    Log("Renaming instances")
562
    rename = self.opts.rename
563
    for instance in self.instances:
564
      Log("instance %s" % instance, indent=1)
565
      self.StopInstance(instance)
566
      self.RenameInstance(instance, rename)
567
      self.StartInstance(rename)
568
      self._CheckInstanceAlive(rename)
569
      self.StopInstance(rename)
570
      self.RenameInstance(rename, instance)
571
      self.StartInstance(instance)
572

    
573
    for instance in self.instances:
574
      self._CheckInstanceAlive(instance)
575

    
576
  def Reinstall(self):
577
    """Reinstall the instances."""
578
    Log("Reinstalling instances")
579
    for instance in self.instances:
580
      Log("instance %s" % instance, indent=1)
581
      self.StopInstance(instance)
582
      op = opcodes.OpReinstallInstance(instance_name=instance)
583
      Log("reinstall without passing the OS", indent=2)
584
      self.ExecOp(op)
585
      op = opcodes.OpReinstallInstance(instance_name=instance,
586
                                       os_type=self.opts.os)
587
      Log("reinstall specifying the OS", indent=2)
588
      self.ExecOp(op)
589
      self.StartInstance(instance)
590
    for instance in self.instances:
591
      self._CheckInstanceAlive(instance)
592

    
593
  def Reboot(self):
594
    """Reboot the instances."""
595
    Log("Rebooting instances")
596
    for instance in self.instances:
597
      Log("instance %s" % instance, indent=1)
598
      for reboot_type in constants.REBOOT_TYPES:
599
        op = opcodes.OpRebootInstance(instance_name=instance,
600
                                      reboot_type=reboot_type,
601
                                      ignore_secondaries=False)
602
        Log("reboot with type '%s'" % reboot_type, indent=2)
603
        self.ExecOp(op)
604
        self._CheckInstanceAlive(instance)
605

    
606
  def ActivateDisks(self):
607
    """Activate and deactivate disks of the instances."""
608
    Log("Activating/deactivating disks")
609
    for instance in self.instances:
610
      Log("instance %s" % instance, indent=1)
611
      op_act = opcodes.OpActivateInstanceDisks(instance_name=instance)
612
      op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance)
613
      Log("activate disks when online", indent=2)
614
      self.ExecOp(op_act)
615
      self.StopInstance(instance)
616
      Log("activate disks when offline", indent=2)
617
      self.ExecOp(op_act)
618
      Log("deactivate disks (when offline)", indent=2)
619
      self.ExecOp(op_deact)
620
      self.StartInstance(instance)
621
    for instance in self.instances:
622
      self._CheckInstanceAlive(instance)
623

    
624
  def AddRemoveDisks(self):
625
    """Add and remove an extra disk for the instances."""
626
    Log("Adding and removing disks")
627
    for instance in self.instances:
628
      Log("instance %s" % instance, indent=1)
629
      op_add = opcodes.OpSetInstanceParams(\
630
        instance_name=instance,
631
        disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})])
632
      op_rem = opcodes.OpSetInstanceParams(\
633
        instance_name=instance, disks=[(constants.DDM_REMOVE, {})])
634
      Log("adding a disk", indent=2)
635
      self.ExecOp(op_add)
636
      self.StopInstance(instance)
637
      Log("removing last disk", indent=2)
638
      self.ExecOp(op_rem)
639
      self.StartInstance(instance)
640
    for instance in self.instances:
641
      self._CheckInstanceAlive(instance)
642

    
643
  def AddRemoveNICs(self):
644
    """Add and remove an extra NIC for the instances."""
645
    Log("Adding and removing NICs")
646
    for instance in self.instances:
647
      Log("instance %s" % instance, indent=1)
648
      op_add = opcodes.OpSetInstanceParams(\
649
        instance_name=instance, nics=[(constants.DDM_ADD, {})])
650
      op_rem = opcodes.OpSetInstanceParams(\
651
        instance_name=instance, nics=[(constants.DDM_REMOVE, {})])
652
      Log("adding a NIC", indent=2)
653
      self.ExecOp(op_add)
654
      Log("removing last NIC", indent=2)
655
      self.ExecOp(op_rem)
656

    
657
  def _CheckInstanceAlive(self, instance):
658
    """Check if an instance is alive by doing http checks.
659

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

    
665
    """
666
    if not self.opts.http_check:
667
      return
668
    end_time = time.time() + self.opts.net_timeout
669
    url = None
670
    while time.time() < end_time and url is None:
671
      try:
672
        url = self.url_opener.open("http://%s/hostname.txt" % instance)
673
      except IOError, err:
674
        # here we can have connection refused, no route to host, etc.
675
        time.sleep(1)
676
    if url is None:
677
      raise InstanceDown(instance, "Cannot contact instance")
678
    hostname = url.read().strip()
679
    url.close()
680
    if hostname != instance:
681
      raise InstanceDown(instance, ("Hostname mismatch, expected %s, got %s" %
682
                                    (instance, hostname)))
683

    
684
  def BurninCluster(self):
685
    """Test a cluster intensively.
686

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

    
690
    """
691

    
692
    opts = self.opts
693

    
694
    Log("Testing global parameters")
695

    
696
    if (len(self.nodes) == 1 and
697
        opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
698
                                   constants.DT_FILE)):
699
      Err("When one node is available/selected the disk template must"
700
          " be 'diskless', 'file' or 'plain'")
701

    
702
    has_err = True
703
    try:
704
      self.CreateInstances()
705
      if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
706
        self.ReplaceDisks1D8()
707
      if (opts.do_replace2 and len(self.nodes) > 2 and
708
          opts.disk_template in constants.DTS_NET_MIRROR) :
709
        self.ReplaceDisks2()
710

    
711
      if (opts.disk_template != constants.DT_DISKLESS and
712
          utils.any(self.disk_growth, lambda n: n > 0)):
713
        self.GrowDisks()
714

    
715
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
716
        self.Failover()
717

    
718
      if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
719
        self.Migrate()
720

    
721
      if (opts.do_importexport and
722
          opts.disk_template not in (constants.DT_DISKLESS,
723
                                     constants.DT_FILE)):
724
        self.ImportExport()
725

    
726
      if opts.do_reinstall:
727
        self.Reinstall()
728

    
729
      if opts.do_reboot:
730
        self.Reboot()
731

    
732
      if opts.do_addremove_disks:
733
        self.AddRemoveDisks()
734

    
735
      if opts.do_addremove_nics:
736
        self.AddRemoveNICs()
737

    
738
      if opts.do_activate_disks:
739
        self.ActivateDisks()
740

    
741
      if opts.rename:
742
        self.Rename()
743

    
744
      if opts.do_startstop:
745
        self.StopStart()
746

    
747
      has_err = False
748
    finally:
749
      if has_err:
750
        Log("Error detected: opcode buffer follows:\n\n")
751
        Log(self.GetFeedbackBuf())
752
        Log("\n\n")
753
      if not self.opts.keep_instances:
754
        self.Remove()
755

    
756
    return 0
757

    
758

    
759
def main():
760
  """Main function"""
761

    
762
  burner = Burner()
763
  return burner.BurninCluster()
764

    
765

    
766
if __name__ == "__main__":
767
  main()