4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
32 from itertools import izip, islice, cycle
33 from cStringIO import StringIO
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import cli
38 from ganeti import errors
39 from ganeti import utils
42 USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
46 class InstanceDown(Exception):
47 """The checked instance was not up"""
50 class BurninFailure(Exception):
51 """Failure detected during burning"""
55 """Shows program usage information and exits the program."""
57 print >> sys.stderr, "Usage:"
58 print >> sys.stderr, USAGE
62 def Log(msg, indent=0):
63 """Simple function that prints out its argument.
71 sys.stdout.write("%*s%s%s\n" % (2*indent, "",
72 headers.get(indent, " "), msg))
75 def Err(msg, exit_code=1):
76 """Simple error logging that prints to stderr.
79 sys.stderr.write(msg + "\n")
84 class SimpleOpener(urllib.FancyURLopener):
85 """A simple url opener"""
86 # pylint: disable-msg=W0221
88 def prompt_user_passwd(self, host, realm, clear_cache=0):
89 """No-interaction version of prompt_user_passwd."""
90 # we follow parent class' API
91 # pylint: disable-msg=W0613
94 def http_error_default(self, url, fp, errcode, errmsg, headers):
95 """Custom error handling"""
96 # make sure sockets are not left in CLOSE_WAIT, this is similar
97 # but with a different exception to the BasicURLOpener class
98 _ = fp.read() # throw away data
100 raise InstanceDown("HTTP error returned: code %s, msg %s" %
105 cli.cli_option("-o", "--os", dest="os", default=None,
106 help="OS to use during burnin",
108 completion_suggest=cli.OPT_COMPL_ONE_OS),
109 cli.cli_option("--disk-size", dest="disk_size",
110 help="Disk size (determines disk count)",
111 default="128m", type="string", metavar="<size,size,...>",
112 completion_suggest=("128M 512M 1G 4G 1G,256M"
113 " 4G,1G,1G 10G").split()),
114 cli.cli_option("--disk-growth", dest="disk_growth", help="Disk growth",
115 default="128m", type="string", metavar="<size,size,...>"),
116 cli.cli_option("--mem-size", dest="mem_size", help="Memory size",
117 default=128, type="unit", metavar="<size>",
118 completion_suggest=("128M 256M 512M 1G 4G 8G"
119 " 12G 16G").split()),
123 cli.cli_option("--no-replace1", dest="do_replace1",
124 help="Skip disk replacement with the same secondary",
125 action="store_false", default=True),
126 cli.cli_option("--no-replace2", dest="do_replace2",
127 help="Skip disk replacement with a different secondary",
128 action="store_false", default=True),
129 cli.cli_option("--no-failover", dest="do_failover",
130 help="Skip instance failovers", action="store_false",
132 cli.cli_option("--no-migrate", dest="do_migrate",
133 help="Skip instance live migration",
134 action="store_false", default=True),
135 cli.cli_option("--no-move", dest="do_move",
136 help="Skip instance moves", action="store_false",
138 cli.cli_option("--no-importexport", dest="do_importexport",
139 help="Skip instance export/import", action="store_false",
141 cli.cli_option("--no-startstop", dest="do_startstop",
142 help="Skip instance stop/start", action="store_false",
144 cli.cli_option("--no-reinstall", dest="do_reinstall",
145 help="Skip instance reinstall", action="store_false",
147 cli.cli_option("--no-reboot", dest="do_reboot",
148 help="Skip instance reboot", action="store_false",
150 cli.cli_option("--no-activate-disks", dest="do_activate_disks",
151 help="Skip disk activation/deactivation",
152 action="store_false", default=True),
153 cli.cli_option("--no-add-disks", dest="do_addremove_disks",
154 help="Skip disk addition/removal",
155 action="store_false", default=True),
156 cli.cli_option("--no-add-nics", dest="do_addremove_nics",
157 help="Skip NIC addition/removal",
158 action="store_false", default=True),
159 cli.cli_option("--no-nics", dest="nics",
160 help="No network interfaces", action="store_const",
161 const=[], default=[{}]),
162 cli.cli_option("--rename", dest="rename", default=None,
163 help=("Give one unused instance name which is taken"
164 " to start the renaming sequence"),
165 metavar="<instance_name>"),
166 cli.cli_option("-t", "--disk-template", dest="disk_template",
167 choices=list(constants.DISK_TEMPLATES),
168 default=constants.DT_DRBD8,
169 help="Disk template (diskless, file, plain or drbd) [drbd]"),
170 cli.cli_option("-n", "--nodes", dest="nodes", default="",
171 help=("Comma separated list of nodes to perform"
172 " the burnin on (defaults to all nodes)"),
173 completion_suggest=cli.OPT_COMPL_MANY_NODES),
174 cli.cli_option("-I", "--iallocator", dest="iallocator",
175 default=None, type="string",
176 help=("Perform the allocation using an iallocator"
177 " instead of fixed node spread (node restrictions no"
178 " longer apply, therefore -n/--nodes must not be"
180 completion_suggest=cli.OPT_COMPL_ONE_IALLOCATOR),
181 cli.cli_option("-p", "--parallel", default=False, action="store_true",
183 help=("Enable parallelization of some operations in"
184 " order to speed burnin or to test granular locking")),
185 cli.cli_option("--net-timeout", default=15, type="int",
187 help=("The instance check network timeout in seconds"
188 " (defaults to 15 seconds)"),
189 completion_suggest="15 60 300 900".split()),
190 cli.cli_option("-C", "--http-check", default=False, action="store_true",
192 help=("Enable checking of instance status via http,"
193 " looking for /hostname.txt that should contain the"
194 " name of the instance")),
195 cli.cli_option("-K", "--keep-instances", default=False,
197 dest="keep_instances",
198 help=("Leave instances on the cluster after burnin,"
199 " for investigation in case of errors or simply"
203 # Mainly used for bash completion
204 ARGUMENTS = [cli.ArgInstance(min=1)]
207 def _DoCheckInstances(fn):
208 """Decorator for checking instances.
211 def wrapper(self, *args, **kwargs):
212 val = fn(self, *args, **kwargs)
213 for instance in self.instances:
214 self._CheckInstanceAlive(instance) # pylint: disable-msg=W0212
221 """Decorator for possible batch operations.
223 Must come after the _DoCheckInstances decorator (if any).
225 @param retry: whether this is a retryable batch, will be
230 def batched(self, *args, **kwargs):
231 self.StartBatch(retry)
232 val = fn(self, *args, **kwargs)
240 class Burner(object):
245 utils.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True)
246 self.url_opener = SimpleOpener()
247 self._feed_buf = StringIO()
253 self.queue_retry = False
254 self.disk_count = self.disk_growth = self.disk_size = None
255 self.hvp = self.bep = None
257 self.cl = cli.GetClient()
260 def ClearFeedbackBuf(self):
261 """Clear the feedback buffer."""
262 self._feed_buf.truncate(0)
264 def GetFeedbackBuf(self):
265 """Return the contents of the buffer."""
266 return self._feed_buf.getvalue()
268 def Feedback(self, msg):
269 """Acumulate feedback in our buffer."""
270 formatted_msg = "%s %s" % (time.ctime(utils.MergeTime(msg[0])), msg[2])
271 self._feed_buf.write(formatted_msg + "\n")
272 if self.opts.verbose:
273 Log(formatted_msg, indent=3)
275 def MaybeRetry(self, retry_count, msg, fn, *args):
276 """Possibly retry a given function execution.
278 @type retry_count: int
279 @param retry_count: retry counter:
280 - 0: non-retryable action
281 - 1: last retry for a retryable action
282 - MAX_RETRIES: original try for a retryable action
284 @param msg: the kind of the operation
286 @param fn: the function to be called
291 if retry_count > 0 and retry_count < MAX_RETRIES:
292 Log("Idempotent %s succeeded after %d retries" %
293 (msg, MAX_RETRIES - retry_count))
295 except Exception, err: # pylint: disable-msg=W0703
297 Log("Non-idempotent %s failed, aborting" % (msg, ))
299 elif retry_count == 1:
300 Log("Idempotent %s repeated failure, aborting" % (msg, ))
303 Log("Idempotent %s failed, retry #%d/%d: %s" %
304 (msg, MAX_RETRIES - retry_count + 1, MAX_RETRIES, err))
305 self.MaybeRetry(retry_count - 1, msg, fn, *args)
307 def _ExecOp(self, *ops):
308 """Execute one or more opcodes and manage the exec buffer.
310 @result: if only opcode has been passed, we return its result;
311 otherwise we return the list of results
314 job_id = cli.SendJob(ops, cl=self.cl)
315 results = cli.PollJob(job_id, cl=self.cl, feedback_fn=self.Feedback)
321 def ExecOp(self, retry, *ops):
322 """Execute one or more opcodes and manage the exec buffer.
324 @result: if only opcode has been passed, we return its result;
325 otherwise we return the list of results
332 return self.MaybeRetry(rval, "opcode", self._ExecOp, *ops)
334 def ExecOrQueue(self, name, *ops):
335 """Execute an opcode and manage the exec buffer."""
336 if self.opts.parallel:
337 self.queued_ops.append((ops, name))
339 return self.ExecOp(self.queue_retry, *ops)
341 def StartBatch(self, retry):
342 """Start a new batch of jobs.
344 @param retry: whether this is a retryable batch
348 self.queue_retry = retry
350 def CommitQueue(self):
351 """Execute all submitted opcodes in case of parallel burnin"""
352 if not self.opts.parallel:
361 results = self.MaybeRetry(rval, "jobset", self.ExecJobSet,
367 def ExecJobSet(self, jobs):
368 """Execute a set of jobs and return once all are done.
370 The method will return the list of results, if all jobs are
371 successful. Otherwise, OpExecError will be raised from within
375 self.ClearFeedbackBuf()
376 job_ids = [cli.SendJob(row[0], cl=self.cl) for row in jobs]
377 Log("Submitted job ID(s) %s" % utils.CommaJoin(job_ids), indent=1)
379 for jid, (_, iname) in zip(job_ids, jobs):
380 Log("waiting for job %s for %s" % (jid, iname), indent=2)
382 results.append(cli.PollJob(jid, cl=self.cl, feedback_fn=self.Feedback))
383 except Exception, err: # pylint: disable-msg=W0703
384 Log("Job for %s failed: %s" % (iname, err))
385 if len(results) != len(jobs):
386 raise BurninFailure()
389 def ParseOptions(self):
390 """Parses the command line options.
392 In case of command line errors, it will show the usage and exit the
396 parser = optparse.OptionParser(usage="\n%s" % USAGE,
397 version=("%%prog (ganeti) %s" %
398 constants.RELEASE_VERSION),
401 options, args = parser.parse_args()
402 if len(args) < 1 or options.os is None:
405 supported_disk_templates = (constants.DT_DISKLESS,
409 if options.disk_template not in supported_disk_templates:
410 Err("Unknown disk template '%s'" % options.disk_template)
412 if options.disk_template == constants.DT_DISKLESS:
413 disk_size = disk_growth = []
414 options.do_addremove_disks = False
416 disk_size = [utils.ParseUnit(v) for v in options.disk_size.split(",")]
417 disk_growth = [utils.ParseUnit(v)
418 for v in options.disk_growth.split(",")]
419 if len(disk_growth) != len(disk_size):
420 Err("Wrong disk sizes/growth combination")
421 if ((disk_size and options.disk_template == constants.DT_DISKLESS) or
422 (not disk_size and options.disk_template != constants.DT_DISKLESS)):
423 Err("Wrong disk count/disk template combination")
425 self.disk_size = disk_size
426 self.disk_growth = disk_growth
427 self.disk_count = len(disk_size)
429 if options.nodes and options.iallocator:
430 Err("Give either the nodes option or the iallocator option, not both")
432 if options.http_check and not options.name_check:
433 Err("Can't enable HTTP checks without name checks")
436 self.instances = args
438 constants.BE_MEMORY: options.mem_size,
439 constants.BE_VCPUS: 1,
443 socket.setdefaulttimeout(options.net_timeout)
446 """Read the cluster state from the config."""
448 names = self.opts.nodes.split(",")
452 op = opcodes.OpQueryNodes(output_fields=["name", "offline", "drained"],
453 names=names, use_locking=True)
454 result = self.ExecOp(True, op)
455 except errors.GenericError, err:
456 err_code, msg = cli.FormatError(err)
457 Err(msg, exit_code=err_code)
458 self.nodes = [data[0] for data in result if not (data[1] or data[2])]
460 op_diagnose = opcodes.OpDiagnoseOS(output_fields=["name", "valid",
461 "variants"], names=[])
462 result = self.ExecOp(True, op_diagnose)
465 Err("Can't get the OS list")
468 for (name, valid, variants) in result:
469 if valid and self.opts.os in cli.CalculateOSNames(name, variants):
474 Err("OS '%s' not found" % self.opts.os)
478 def BurnCreateInstances(self):
479 """Create the given instances.
483 mytor = izip(cycle(self.nodes),
484 islice(cycle(self.nodes), 1, None),
487 Log("Creating instances")
488 for pnode, snode, instance in mytor:
489 Log("instance %s" % instance, indent=1)
490 if self.opts.iallocator:
492 msg = "with iallocator %s" % self.opts.iallocator
493 elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
495 msg = "on %s" % pnode
497 msg = "on %s, %s" % (pnode, snode)
501 op = opcodes.OpCreateInstance(instance_name=instance,
502 disks = [ {"size": size}
503 for size in self.disk_size],
504 disk_template=self.opts.disk_template,
506 mode=constants.INSTANCE_CREATE,
507 os_type=self.opts.os,
511 ip_check=self.opts.ip_check,
512 name_check=self.opts.name_check,
515 file_storage_dir=None,
516 iallocator=self.opts.iallocator,
521 self.ExecOrQueue(instance, op)
522 self.to_rem.append(instance)
525 def BurnGrowDisks(self):
526 """Grow both the os and the swap disks by the requested amount, if any."""
528 for instance in self.instances:
529 Log("instance %s" % instance, indent=1)
530 for idx, growth in enumerate(self.disk_growth):
532 op = opcodes.OpGrowDisk(instance_name=instance, disk=idx,
533 amount=growth, wait_for_sync=True)
534 Log("increase disk/%s by %s MB" % (idx, growth), indent=2)
535 self.ExecOrQueue(instance, op)
538 def BurnReplaceDisks1D8(self):
539 """Replace disks on primary and secondary for drbd8."""
540 Log("Replacing disks on the same nodes")
541 for instance in self.instances:
542 Log("instance %s" % instance, indent=1)
544 for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
545 op = opcodes.OpReplaceDisks(instance_name=instance,
547 disks=[i for i in range(self.disk_count)])
548 Log("run %s" % mode, indent=2)
550 self.ExecOrQueue(instance, *ops) # pylint: disable-msg=W0142
553 def BurnReplaceDisks2(self):
554 """Replace secondary node."""
555 Log("Changing the secondary node")
556 mode = constants.REPLACE_DISK_CHG
558 mytor = izip(islice(cycle(self.nodes), 2, None),
560 for tnode, instance in mytor:
561 Log("instance %s" % instance, indent=1)
562 if self.opts.iallocator:
564 msg = "with iallocator %s" % self.opts.iallocator
567 op = opcodes.OpReplaceDisks(instance_name=instance,
570 iallocator=self.opts.iallocator,
572 Log("run %s %s" % (mode, msg), indent=2)
573 self.ExecOrQueue(instance, op)
577 def BurnFailover(self):
578 """Failover the instances."""
579 Log("Failing over instances")
580 for instance in self.instances:
581 Log("instance %s" % instance, indent=1)
582 op = opcodes.OpFailoverInstance(instance_name=instance,
583 ignore_consistency=False)
584 self.ExecOrQueue(instance, op)
589 """Move the instances."""
590 Log("Moving instances")
591 mytor = izip(islice(cycle(self.nodes), 1, None),
593 for tnode, instance in mytor:
594 Log("instance %s" % instance, indent=1)
595 op = opcodes.OpMoveInstance(instance_name=instance,
597 self.ExecOrQueue(instance, op)
600 def BurnMigrate(self):
601 """Migrate the instances."""
602 Log("Migrating instances")
603 for instance in self.instances:
604 Log("instance %s" % instance, indent=1)
605 op1 = opcodes.OpMigrateInstance(instance_name=instance, live=True,
608 op2 = opcodes.OpMigrateInstance(instance_name=instance, live=True,
610 Log("migration and migration cleanup", indent=2)
611 self.ExecOrQueue(instance, op1, op2)
615 def BurnImportExport(self):
616 """Export the instance, delete it, and import it back.
619 Log("Exporting and re-importing instances")
620 mytor = izip(cycle(self.nodes),
621 islice(cycle(self.nodes), 1, None),
622 islice(cycle(self.nodes), 2, None),
625 for pnode, snode, enode, instance in mytor:
626 Log("instance %s" % instance, indent=1)
627 # read the full name of the instance
628 nam_op = opcodes.OpQueryInstances(output_fields=["name"],
629 names=[instance], use_locking=True)
630 full_name = self.ExecOp(False, nam_op)[0][0]
632 if self.opts.iallocator:
634 import_log_msg = ("import from %s"
635 " with iallocator %s" %
636 (enode, self.opts.iallocator))
637 elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
639 import_log_msg = ("import from %s to %s" %
642 import_log_msg = ("import from %s to %s, %s" %
643 (enode, pnode, snode))
645 exp_op = opcodes.OpExportInstance(instance_name=instance,
648 rem_op = opcodes.OpRemoveInstance(instance_name=instance,
649 ignore_failures=True)
650 imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
651 imp_op = opcodes.OpCreateInstance(instance_name=instance,
652 disks = [ {"size": size}
653 for size in self.disk_size],
654 disk_template=self.opts.disk_template,
656 mode=constants.INSTANCE_IMPORT,
662 ip_check=self.opts.ip_check,
663 name_check=self.opts.name_check,
665 file_storage_dir=None,
667 iallocator=self.opts.iallocator,
672 erem_op = opcodes.OpRemoveExport(instance_name=instance)
674 Log("export to node %s" % enode, indent=2)
675 Log("remove instance", indent=2)
676 Log(import_log_msg, indent=2)
677 Log("remove export", indent=2)
678 self.ExecOrQueue(instance, exp_op, rem_op, imp_op, erem_op)
681 def StopInstanceOp(instance):
682 """Stop given instance."""
683 return opcodes.OpShutdownInstance(instance_name=instance)
686 def StartInstanceOp(instance):
687 """Start given instance."""
688 return opcodes.OpStartupInstance(instance_name=instance, force=False)
691 def RenameInstanceOp(instance, instance_new):
692 """Rename instance."""
693 return opcodes.OpRenameInstance(instance_name=instance,
694 new_name=instance_new)
698 def BurnStopStart(self):
699 """Stop/start the instances."""
700 Log("Stopping and starting instances")
701 for instance in self.instances:
702 Log("instance %s" % instance, indent=1)
703 op1 = self.StopInstanceOp(instance)
704 op2 = self.StartInstanceOp(instance)
705 self.ExecOrQueue(instance, op1, op2)
708 def BurnRemove(self):
709 """Remove the instances."""
710 Log("Removing instances")
711 for instance in self.to_rem:
712 Log("instance %s" % instance, indent=1)
713 op = opcodes.OpRemoveInstance(instance_name=instance,
714 ignore_failures=True)
715 self.ExecOrQueue(instance, op)
717 def BurnRename(self):
718 """Rename the instances.
720 Note that this function will not execute in parallel, since we
721 only have one target for rename.
724 Log("Renaming instances")
725 rename = self.opts.rename
726 for instance in self.instances:
727 Log("instance %s" % instance, indent=1)
728 op_stop1 = self.StopInstanceOp(instance)
729 op_stop2 = self.StopInstanceOp(rename)
730 op_rename1 = self.RenameInstanceOp(instance, rename)
731 op_rename2 = self.RenameInstanceOp(rename, instance)
732 op_start1 = self.StartInstanceOp(rename)
733 op_start2 = self.StartInstanceOp(instance)
734 self.ExecOp(False, op_stop1, op_rename1, op_start1)
735 self._CheckInstanceAlive(rename)
736 self.ExecOp(False, op_stop2, op_rename2, op_start2)
737 self._CheckInstanceAlive(instance)
741 def BurnReinstall(self):
742 """Reinstall the instances."""
743 Log("Reinstalling instances")
744 for instance in self.instances:
745 Log("instance %s" % instance, indent=1)
746 op1 = self.StopInstanceOp(instance)
747 op2 = opcodes.OpReinstallInstance(instance_name=instance)
748 Log("reinstall without passing the OS", indent=2)
749 op3 = opcodes.OpReinstallInstance(instance_name=instance,
750 os_type=self.opts.os)
751 Log("reinstall specifying the OS", indent=2)
752 op4 = self.StartInstanceOp(instance)
753 self.ExecOrQueue(instance, op1, op2, op3, op4)
757 def BurnReboot(self):
758 """Reboot the instances."""
759 Log("Rebooting instances")
760 for instance in self.instances:
761 Log("instance %s" % instance, indent=1)
763 for reboot_type in constants.REBOOT_TYPES:
764 op = opcodes.OpRebootInstance(instance_name=instance,
765 reboot_type=reboot_type,
766 ignore_secondaries=False)
767 Log("reboot with type '%s'" % reboot_type, indent=2)
769 self.ExecOrQueue(instance, *ops) # pylint: disable-msg=W0142
773 def BurnActivateDisks(self):
774 """Activate and deactivate disks of the instances."""
775 Log("Activating/deactivating disks")
776 for instance in self.instances:
777 Log("instance %s" % instance, indent=1)
778 op_start = self.StartInstanceOp(instance)
779 op_act = opcodes.OpActivateInstanceDisks(instance_name=instance)
780 op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance)
781 op_stop = self.StopInstanceOp(instance)
782 Log("activate disks when online", indent=2)
783 Log("activate disks when offline", indent=2)
784 Log("deactivate disks (when offline)", indent=2)
785 self.ExecOrQueue(instance, op_act, op_stop, op_act, op_deact, op_start)
789 def BurnAddRemoveDisks(self):
790 """Add and remove an extra disk for the instances."""
791 Log("Adding and removing disks")
792 for instance in self.instances:
793 Log("instance %s" % instance, indent=1)
794 op_add = opcodes.OpSetInstanceParams(\
795 instance_name=instance,
796 disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})])
797 op_rem = opcodes.OpSetInstanceParams(\
798 instance_name=instance, disks=[(constants.DDM_REMOVE, {})])
799 op_stop = self.StopInstanceOp(instance)
800 op_start = self.StartInstanceOp(instance)
801 Log("adding a disk", indent=2)
802 Log("removing last disk", indent=2)
803 self.ExecOrQueue(instance, op_add, op_stop, op_rem, op_start)
806 def BurnAddRemoveNICs(self):
807 """Add and remove an extra NIC for the instances."""
808 Log("Adding and removing NICs")
809 for instance in self.instances:
810 Log("instance %s" % instance, indent=1)
811 op_add = opcodes.OpSetInstanceParams(\
812 instance_name=instance, nics=[(constants.DDM_ADD, {})])
813 op_rem = opcodes.OpSetInstanceParams(\
814 instance_name=instance, nics=[(constants.DDM_REMOVE, {})])
815 Log("adding a NIC", indent=2)
816 Log("removing last NIC", indent=2)
817 self.ExecOrQueue(instance, op_add, op_rem)
819 def _CheckInstanceAlive(self, instance):
820 """Check if an instance is alive by doing http checks.
822 This will try to retrieve the url on the instance /hostname.txt
823 and check that it contains the hostname of the instance. In case
824 we get ECONNREFUSED, we retry up to the net timeout seconds, for
825 any other error we abort.
828 if not self.opts.http_check:
830 end_time = time.time() + self.opts.net_timeout
832 while time.time() < end_time and url is None:
834 url = self.url_opener.open("http://%s/hostname.txt" % instance)
836 # here we can have connection refused, no route to host, etc.
839 raise InstanceDown(instance, "Cannot contact instance")
840 hostname = url.read().strip()
842 if hostname != instance:
843 raise InstanceDown(instance, ("Hostname mismatch, expected %s, got %s" %
844 (instance, hostname)))
846 def BurninCluster(self):
847 """Test a cluster intensively.
849 This will create instances and then start/stop/failover them.
850 It is safe for existing instances but could impact performance.
856 Log("Testing global parameters")
858 if (len(self.nodes) == 1 and
859 opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
861 Err("When one node is available/selected the disk template must"
862 " be 'diskless', 'file' or 'plain'")
866 self.BurnCreateInstances()
867 if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
868 self.BurnReplaceDisks1D8()
869 if (opts.do_replace2 and len(self.nodes) > 2 and
870 opts.disk_template in constants.DTS_NET_MIRROR) :
871 self.BurnReplaceDisks2()
873 if (opts.disk_template != constants.DT_DISKLESS and
874 utils.any(self.disk_growth, lambda n: n > 0)):
877 if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
880 if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
883 if (opts.do_move and len(self.nodes) > 1 and
884 opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):
887 if (opts.do_importexport and
888 opts.disk_template not in (constants.DT_DISKLESS,
890 self.BurnImportExport()
892 if opts.do_reinstall:
898 if opts.do_addremove_disks:
899 self.BurnAddRemoveDisks()
901 if opts.do_addremove_nics:
902 self.BurnAddRemoveNICs()
904 if opts.do_activate_disks:
905 self.BurnActivateDisks()
910 if opts.do_startstop:
916 Log("Error detected: opcode buffer follows:\n\n")
917 Log(self.GetFeedbackBuf())
919 if not self.opts.keep_instances:
922 except Exception, err: # pylint: disable-msg=W0703
923 if has_err: # already detected errors, so errors in removal
925 Log("Note: error detected during instance remove: %s" % str(err))
926 else: # non-expected error
936 return burner.BurninCluster()
939 if __name__ == "__main__":