Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ aa089b65

History | View | Annotate | Download (26.9 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 urllib2
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
class Burner(object):
81
  """Burner class."""
82

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

    
95
  def ClearFeedbackBuf(self):
96
    """Clear the feedback buffer."""
97
    self._feed_buf.truncate(0)
98

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

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

    
110
  def ExecOp(self, op):
111
    """Execute an opcode and manage the exec buffer."""
112
    self.ClearFeedbackBuf()
113
    return cli.SubmitOpCode(op, feedback_fn=self.Feedback, cl=self.cl)
114

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

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

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

    
131
    return results
132

    
133
  def ParseOptions(self):
134
    """Parses the command line options.
135

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

    
139
    """
140

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

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

    
232

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

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

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

    
257
    self.disk_size = disk_size
258
    self.disk_growth = disk_growth
259
    self.disk_count = len(disk_size)
260

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

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

    
272
    socket.setdefaulttimeout(options.net_timeout)
273

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

    
288
    result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
289
                                              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
  def CreateInstances(self):
301
    """Create the given instances.
302

    
303
    """
304
    self.to_rem = []
305
    mytor = izip(cycle(self.nodes),
306
                 islice(cycle(self.nodes), 1, None),
307
                 self.instances)
308
    jobset = []
309

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

    
322
      Log(msg, indent=2)
323

    
324
      op = opcodes.OpCreateInstance(instance_name=instance,
325
                                    disks = [ {"size": size}
326
                                              for size in self.disk_size],
327
                                    disk_template=self.opts.disk_template,
328
                                    nics=self.opts.nics,
329
                                    mode=constants.INSTANCE_CREATE,
330
                                    os_type=self.opts.os,
331
                                    pnode=pnode,
332
                                    snode=snode,
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,
339
                                    beparams=self.bep,
340
                                    hvparams=self.hvp,
341
                                    )
342

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

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

    
357
  def GrowDisks(self):
358
    """Grow both the os and the swap disks by the requested amount, if any."""
359
    Log("Growing disks")
360
    for instance in self.instances:
361
      Log("instance %s" % instance, indent=1)
362
      for idx, growth in enumerate(self.disk_growth):
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)
367
          self.ExecOp(op)
368

    
369
  def ReplaceDisks1D8(self):
370
    """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
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
375
        op = opcodes.OpReplaceDisks(instance_name=instance,
376
                                    mode=mode,
377
                                    disks=[i for i in range(self.disk_count)])
378
        Log("run %s" % mode, indent=2)
379
        self.ExecOp(op)
380

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

    
386
    mytor = izip(islice(cycle(self.nodes), 2, None),
387
                 self.instances)
388
    for tnode, instance in mytor:
389
      Log("instance %s" % instance, indent=1)
390
      if self.opts.iallocator:
391
        tnode = None
392
        msg = "with iallocator %s" % self.opts.iallocator
393
      else:
394
        msg = tnode
395
      op = opcodes.OpReplaceDisks(instance_name=instance,
396
                                  mode=mode,
397
                                  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)
401
      self.ExecOp(op)
402

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

    
411
      self.ExecOp(op)
412
    for instance in self.instances:
413
      self._CheckInstanceAlive(instance)
414

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

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

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

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

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

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

    
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
      self.ExecOp(rem_op)
490
      self.to_rem.remove(instance)
491
      Log(import_log_msg, indent=2)
492
      self.ExecOp(imp_op)
493
      Log("remove export", indent=2)
494
      self.ExecOp(erem_op)
495

    
496
      self.to_rem.append(instance)
497

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

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

    
507
  def StartInstance(self, instance):
508
    """Start given instance."""
509
    op = opcodes.OpStartupInstance(instance_name=instance, force=False)
510
    Log("startup", indent=2)
511
    self.ExecOp(op)
512

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

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

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

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

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

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

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

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

    
587
  def ActivateDisks(self):
588
    """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
      op_act = opcodes.OpActivateInstanceDisks(instance_name=instance)
593
      op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance)
594
      Log("activate disks when online", indent=2)
595
      self.ExecOp(op_act)
596
      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
      self.ExecOp(op_deact)
601
      self.StartInstance(instance)
602
    for instance in self.instances:
603
      self._CheckInstanceAlive(instance)
604

    
605
  def AddRemoveDisks(self):
606
    """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
      op_add = opcodes.OpSetInstanceParams(\
611
        instance_name=instance,
612
        disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})])
613
      op_rem = opcodes.OpSetInstanceParams(\
614
        instance_name=instance, disks=[(constants.DDM_REMOVE, {})])
615
      Log("adding a disk", indent=2)
616
      self.ExecOp(op_add)
617
      self.StopInstance(instance)
618
      Log("removing last disk", indent=2)
619
      self.ExecOp(op_rem)
620
      self.StartInstance(instance)
621
    for instance in self.instances:
622
      self._CheckInstanceAlive(instance)
623

    
624
  def AddRemoveNICs(self):
625
    """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
      op_add = opcodes.OpSetInstanceParams(\
630
        instance_name=instance, nics=[(constants.DDM_ADD, {})])
631
      op_rem = opcodes.OpSetInstanceParams(\
632
        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
      self.ExecOp(op_rem)
637

    
638
  def _CheckInstanceAlive(self, instance):
639
    """Check if an instance is alive by doing http checks.
640

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

    
646
    """
647
    if not self.opts.http_check:
648
      return
649
    try:
650
      for retries in range(self.opts.net_timeout):
651
        try:
652
          url = urllib2.urlopen("http://%s/hostname.txt" % instance)
653
        except urllib2.URLError, err:
654
          if err.args[0][0] == errno.ECONNREFUSED:
655
            time.sleep(1)
656
            continue
657
          raise
658
    except urllib2.URLError, err:
659
      raise InstanceDown(instance, str(err))
660
    hostname = url.read().strip()
661
    if hostname != instance:
662
      raise InstanceDown(instance, ("Hostname mismatch, expected %s, got %s" %
663
                                    (instance, hostname)))
664

    
665
  def BurninCluster(self):
666
    """Test a cluster intensively.
667

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

    
671
    """
672

    
673
    opts = self.opts
674

    
675
    Log("Testing global parameters")
676

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

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

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

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

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

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

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

    
710
      if opts.do_reboot:
711
        self.Reboot()
712

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

    
716
      if opts.do_addremove_nics:
717
        self.AddRemoveNICs()
718

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

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

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

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

    
737
    return 0
738

    
739

    
740
def main():
741
  """Main function"""
742

    
743
  burner = Burner()
744
  return burner.BurninCluster()
745

    
746

    
747
if __name__ == "__main__":
748
  main()