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."""
92 def http_error_default(self, url, fp, errcode, errmsg, headers):
93 """Custom error handling"""
94 # make sure sockets are not left in CLOSE_WAIT, this is similar
95 # but with a different exception to the BasicURLOpener class
96 _ = fp.read() # throw away data
98 raise InstanceDown("HTTP error returned: code %s, msg %s" %
103 cli.cli_option("-o", "--os", dest="os", default=None,
104 help="OS to use during burnin",
106 completion_suggest=cli.OPT_COMPL_ONE_OS),
107 cli.cli_option("--disk-size", dest="disk_size",
108 help="Disk size (determines disk count)",
109 default="128m", type="string", metavar="<size,size,...>",
110 completion_suggest=("128M 512M 1G 4G 1G,256M"
111 " 4G,1G,1G 10G").split()),
112 cli.cli_option("--disk-growth", dest="disk_growth", help="Disk growth",
113 default="128m", type="string", metavar="<size,size,...>"),
114 cli.cli_option("--mem-size", dest="mem_size", help="Memory size",
115 default=128, type="unit", metavar="<size>",
116 completion_suggest=("128M 256M 512M 1G 4G 8G"
117 " 12G 16G").split()),
121 cli.cli_option("--no-replace1", dest="do_replace1",
122 help="Skip disk replacement with the same secondary",
123 action="store_false", default=True),
124 cli.cli_option("--no-replace2", dest="do_replace2",
125 help="Skip disk replacement with a different secondary",
126 action="store_false", default=True),
127 cli.cli_option("--no-failover", dest="do_failover",
128 help="Skip instance failovers", action="store_false",
130 cli.cli_option("--no-migrate", dest="do_migrate",
131 help="Skip instance live migration",
132 action="store_false", default=True),
133 cli.cli_option("--no-move", dest="do_move",
134 help="Skip instance moves", action="store_false",
136 cli.cli_option("--no-importexport", dest="do_importexport",
137 help="Skip instance export/import", action="store_false",
139 cli.cli_option("--no-startstop", dest="do_startstop",
140 help="Skip instance stop/start", action="store_false",
142 cli.cli_option("--no-reinstall", dest="do_reinstall",
143 help="Skip instance reinstall", action="store_false",
145 cli.cli_option("--no-reboot", dest="do_reboot",
146 help="Skip instance reboot", action="store_false",
148 cli.cli_option("--no-activate-disks", dest="do_activate_disks",
149 help="Skip disk activation/deactivation",
150 action="store_false", default=True),
151 cli.cli_option("--no-add-disks", dest="do_addremove_disks",
152 help="Skip disk addition/removal",
153 action="store_false", default=True),
154 cli.cli_option("--no-add-nics", dest="do_addremove_nics",
155 help="Skip NIC addition/removal",
156 action="store_false", default=True),
157 cli.cli_option("--no-nics", dest="nics",
158 help="No network interfaces", action="store_const",
159 const=[], default=[{}]),
160 cli.cli_option("--rename", dest="rename", default=None,
161 help=("Give one unused instance name which is taken"
162 " to start the renaming sequence"),
163 metavar="<instance_name>"),
164 cli.cli_option("-t", "--disk-template", dest="disk_template",
165 choices=list(constants.DISK_TEMPLATES),
166 default=constants.DT_DRBD8,
167 help="Disk template (diskless, file, plain or drbd) [drbd]"),
168 cli.cli_option("-n", "--nodes", dest="nodes", default="",
169 help=("Comma separated list of nodes to perform"
170 " the burnin on (defaults to all nodes)"),
171 completion_suggest=cli.OPT_COMPL_MANY_NODES),
172 cli.cli_option("-I", "--iallocator", dest="iallocator",
173 default=None, type="string",
174 help=("Perform the allocation using an iallocator"
175 " instead of fixed node spread (node restrictions no"
176 " longer apply, therefore -n/--nodes must not be"
178 completion_suggest=cli.OPT_COMPL_ONE_IALLOCATOR),
179 cli.cli_option("-p", "--parallel", default=False, action="store_true",
181 help=("Enable parallelization of some operations in"
182 " order to speed burnin or to test granular locking")),
183 cli.cli_option("--net-timeout", default=15, type="int",
185 help=("The instance check network timeout in seconds"
186 " (defaults to 15 seconds)"),
187 completion_suggest="15 60 300 900".split()),
188 cli.cli_option("-C", "--http-check", default=False, action="store_true",
190 help=("Enable checking of instance status via http,"
191 " looking for /hostname.txt that should contain the"
192 " name of the instance")),
193 cli.cli_option("-K", "--keep-instances", default=False,
195 dest="keep_instances",
196 help=("Leave instances on the cluster after burnin,"
197 " for investigation in case of errors or simply"
201 # Mainly used for bash completion
202 ARGUMENTS = [cli.ArgInstance(min=1)]
205 def _DoCheckInstances(fn):
206 """Decorator for checking instances.
209 def wrapper(self, *args, **kwargs):
210 val = fn(self, *args, **kwargs)
211 for instance in self.instances:
212 self._CheckInstanceAlive(instance) # pylint: disable-msg=W0212
219 """Decorator for possible batch operations.
221 Must come after the _DoCheckInstances decorator (if any).
223 @param retry: whether this is a retryable batch, will be
228 def batched(self, *args, **kwargs):
229 self.StartBatch(retry)
230 val = fn(self, *args, **kwargs)
238 class Burner(object):
243 utils.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True)
244 self.url_opener = SimpleOpener()
245 self._feed_buf = StringIO()
251 self.queue_retry = False
252 self.disk_count = self.disk_growth = self.disk_size = None
253 self.hvp = self.bep = None
255 self.cl = cli.GetClient()
258 def ClearFeedbackBuf(self):
259 """Clear the feedback buffer."""
260 self._feed_buf.truncate(0)
262 def GetFeedbackBuf(self):
263 """Return the contents of the buffer."""
264 return self._feed_buf.getvalue()
266 def Feedback(self, msg):
267 """Acumulate feedback in our buffer."""
268 formatted_msg = "%s %s" % (time.ctime(utils.MergeTime(msg[0])), msg[2])
269 self._feed_buf.write(formatted_msg + "\n")
270 if self.opts.verbose:
271 Log(formatted_msg, indent=3)
273 def MaybeRetry(self, retry_count, msg, fn, *args):
274 """Possibly retry a given function execution.
276 @type retry_count: int
277 @param retry_count: retry counter:
278 - 0: non-retryable action
279 - 1: last retry for a retryable action
280 - MAX_RETRIES: original try for a retryable action
282 @param msg: the kind of the operation
284 @param fn: the function to be called
289 if retry_count > 0 and retry_count < MAX_RETRIES:
290 Log("Idempotent %s succeeded after %d retries" %
291 (msg, MAX_RETRIES - retry_count))
293 except Exception, err: # pylint: disable-msg=W0703
295 Log("Non-idempotent %s failed, aborting" % (msg, ))
297 elif retry_count == 1:
298 Log("Idempotent %s repeated failure, aborting" % (msg, ))
301 Log("Idempotent %s failed, retry #%d/%d: %s" %
302 (msg, MAX_RETRIES - retry_count + 1, MAX_RETRIES, err))
303 self.MaybeRetry(retry_count - 1, msg, fn, *args)
305 def _ExecOp(self, *ops):
306 """Execute one or more opcodes and manage the exec buffer.
308 @result: if only opcode has been passed, we return its result;
309 otherwise we return the list of results
312 job_id = cli.SendJob(ops, cl=self.cl)
313 results = cli.PollJob(job_id, cl=self.cl, feedback_fn=self.Feedback)
319 def ExecOp(self, retry, *ops):
320 """Execute one or more opcodes and manage the exec buffer.
322 @result: if only opcode has been passed, we return its result;
323 otherwise we return the list of results
330 return self.MaybeRetry(rval, "opcode", self._ExecOp, *ops)
332 def ExecOrQueue(self, name, *ops):
333 """Execute an opcode and manage the exec buffer."""
334 if self.opts.parallel:
335 self.queued_ops.append((ops, name))
337 return self.ExecOp(self.queue_retry, *ops)
339 def StartBatch(self, retry):
340 """Start a new batch of jobs.
342 @param retry: whether this is a retryable batch
346 self.queue_retry = retry
348 def CommitQueue(self):
349 """Execute all submitted opcodes in case of parallel burnin"""
350 if not self.opts.parallel:
359 results = self.MaybeRetry(rval, "jobset", self.ExecJobSet,
365 def ExecJobSet(self, jobs):
366 """Execute a set of jobs and return once all are done.
368 The method will return the list of results, if all jobs are
369 successful. Otherwise, OpExecError will be raised from within
373 self.ClearFeedbackBuf()
374 job_ids = [cli.SendJob(row[0], cl=self.cl) for row in jobs]
375 Log("Submitted job ID(s) %s" % utils.CommaJoin(job_ids), indent=1)
377 for jid, (_, iname) in zip(job_ids, jobs):
378 Log("waiting for job %s for %s" % (jid, iname), indent=2)
380 results.append(cli.PollJob(jid, cl=self.cl, feedback_fn=self.Feedback))
381 except Exception, err: # pylint: disable-msg=W0703
382 Log("Job for %s failed: %s" % (iname, err))
383 if len(results) != len(jobs):
384 raise BurninFailure()
387 def ParseOptions(self):
388 """Parses the command line options.
390 In case of command line errors, it will show the usage and exit the
394 parser = optparse.OptionParser(usage="\n%s" % USAGE,
395 version=("%%prog (ganeti) %s" %
396 constants.RELEASE_VERSION),
399 options, args = parser.parse_args()
400 if len(args) < 1 or options.os is None:
403 supported_disk_templates = (constants.DT_DISKLESS,
407 if options.disk_template not in supported_disk_templates:
408 Err("Unknown disk template '%s'" % options.disk_template)
410 if options.disk_template == constants.DT_DISKLESS:
411 disk_size = disk_growth = []
412 options.do_addremove_disks = False
414 disk_size = [utils.ParseUnit(v) for v in options.disk_size.split(",")]
415 disk_growth = [utils.ParseUnit(v)
416 for v in options.disk_growth.split(",")]
417 if len(disk_growth) != len(disk_size):
418 Err("Wrong disk sizes/growth combination")
419 if ((disk_size and options.disk_template == constants.DT_DISKLESS) or
420 (not disk_size and options.disk_template != constants.DT_DISKLESS)):
421 Err("Wrong disk count/disk template combination")
423 self.disk_size = disk_size
424 self.disk_growth = disk_growth
425 self.disk_count = len(disk_size)
427 if options.nodes and options.iallocator:
428 Err("Give either the nodes option or the iallocator option, not both")
430 if options.http_check and not options.name_check:
431 Err("Can't enable HTTP checks without name checks")
434 self.instances = args
436 constants.BE_MEMORY: options.mem_size,
437 constants.BE_VCPUS: 1,
441 socket.setdefaulttimeout(options.net_timeout)
444 """Read the cluster state from the config."""
446 names = self.opts.nodes.split(",")
450 op = opcodes.OpQueryNodes(output_fields=["name", "offline", "drained"],
451 names=names, use_locking=True)
452 result = self.ExecOp(True, op)
453 except errors.GenericError, err:
454 err_code, msg = cli.FormatError(err)
455 Err(msg, exit_code=err_code)
456 self.nodes = [data[0] for data in result if not (data[1] or data[2])]
458 op_diagnose = opcodes.OpDiagnoseOS(output_fields=["name", "valid",
459 "variants"], names=[])
460 result = self.ExecOp(True, op_diagnose)
463 Err("Can't get the OS list")
466 for (name, valid, variants) in result:
467 if valid and self.opts.os in cli.CalculateOSNames(name, variants):
472 Err("OS '%s' not found" % self.opts.os)
476 def BurnCreateInstances(self):
477 """Create the given instances.
481 mytor = izip(cycle(self.nodes),
482 islice(cycle(self.nodes), 1, None),
485 Log("Creating instances")
486 for pnode, snode, instance in mytor:
487 Log("instance %s" % instance, indent=1)
488 if self.opts.iallocator:
490 msg = "with iallocator %s" % self.opts.iallocator
491 elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
493 msg = "on %s" % pnode
495 msg = "on %s, %s" % (pnode, snode)
499 op = opcodes.OpCreateInstance(instance_name=instance,
500 disks = [ {"size": size}
501 for size in self.disk_size],
502 disk_template=self.opts.disk_template,
504 mode=constants.INSTANCE_CREATE,
505 os_type=self.opts.os,
509 ip_check=self.opts.ip_check,
510 name_check=self.opts.name_check,
513 file_storage_dir=None,
514 iallocator=self.opts.iallocator,
519 self.ExecOrQueue(instance, op)
520 self.to_rem.append(instance)
523 def BurnGrowDisks(self):
524 """Grow both the os and the swap disks by the requested amount, if any."""
526 for instance in self.instances:
527 Log("instance %s" % instance, indent=1)
528 for idx, growth in enumerate(self.disk_growth):
530 op = opcodes.OpGrowDisk(instance_name=instance, disk=idx,
531 amount=growth, wait_for_sync=True)
532 Log("increase disk/%s by %s MB" % (idx, growth), indent=2)
533 self.ExecOrQueue(instance, op)
536 def BurnReplaceDisks1D8(self):
537 """Replace disks on primary and secondary for drbd8."""
538 Log("Replacing disks on the same nodes")
539 for instance in self.instances:
540 Log("instance %s" % instance, indent=1)
542 for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
543 op = opcodes.OpReplaceDisks(instance_name=instance,
545 disks=[i for i in range(self.disk_count)])
546 Log("run %s" % mode, indent=2)
548 self.ExecOrQueue(instance, *ops) # pylint: disable-msg=W0142
551 def BurnReplaceDisks2(self):
552 """Replace secondary node."""
553 Log("Changing the secondary node")
554 mode = constants.REPLACE_DISK_CHG
556 mytor = izip(islice(cycle(self.nodes), 2, None),
558 for tnode, instance in mytor:
559 Log("instance %s" % instance, indent=1)
560 if self.opts.iallocator:
562 msg = "with iallocator %s" % self.opts.iallocator
565 op = opcodes.OpReplaceDisks(instance_name=instance,
568 iallocator=self.opts.iallocator,
570 Log("run %s %s" % (mode, msg), indent=2)
571 self.ExecOrQueue(instance, op)
575 def BurnFailover(self):
576 """Failover the instances."""
577 Log("Failing over instances")
578 for instance in self.instances:
579 Log("instance %s" % instance, indent=1)
580 op = opcodes.OpFailoverInstance(instance_name=instance,
581 ignore_consistency=False)
582 self.ExecOrQueue(instance, op)
587 """Move the instances."""
588 Log("Moving instances")
589 mytor = izip(islice(cycle(self.nodes), 1, None),
591 for tnode, instance in mytor:
592 Log("instance %s" % instance, indent=1)
593 op = opcodes.OpMoveInstance(instance_name=instance,
595 self.ExecOrQueue(instance, op)
598 def BurnMigrate(self):
599 """Migrate the instances."""
600 Log("Migrating instances")
601 for instance in self.instances:
602 Log("instance %s" % instance, indent=1)
603 op1 = opcodes.OpMigrateInstance(instance_name=instance, live=True,
606 op2 = opcodes.OpMigrateInstance(instance_name=instance, live=True,
608 Log("migration and migration cleanup", indent=2)
609 self.ExecOrQueue(instance, op1, op2)
613 def BurnImportExport(self):
614 """Export the instance, delete it, and import it back.
617 Log("Exporting and re-importing instances")
618 mytor = izip(cycle(self.nodes),
619 islice(cycle(self.nodes), 1, None),
620 islice(cycle(self.nodes), 2, None),
623 for pnode, snode, enode, instance in mytor:
624 Log("instance %s" % instance, indent=1)
625 # read the full name of the instance
626 nam_op = opcodes.OpQueryInstances(output_fields=["name"],
627 names=[instance], use_locking=True)
628 full_name = self.ExecOp(False, nam_op)[0][0]
630 if self.opts.iallocator:
632 import_log_msg = ("import from %s"
633 " with iallocator %s" %
634 (enode, self.opts.iallocator))
635 elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
637 import_log_msg = ("import from %s to %s" %
640 import_log_msg = ("import from %s to %s, %s" %
641 (enode, pnode, snode))
643 exp_op = opcodes.OpExportInstance(instance_name=instance,
646 rem_op = opcodes.OpRemoveInstance(instance_name=instance,
647 ignore_failures=True)
648 imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
649 imp_op = opcodes.OpCreateInstance(instance_name=instance,
650 disks = [ {"size": size}
651 for size in self.disk_size],
652 disk_template=self.opts.disk_template,
654 mode=constants.INSTANCE_IMPORT,
660 ip_check=self.opts.ip_check,
661 name_check=self.opts.name_check,
663 file_storage_dir=None,
665 iallocator=self.opts.iallocator,
670 erem_op = opcodes.OpRemoveExport(instance_name=instance)
672 Log("export to node %s" % enode, indent=2)
673 Log("remove instance", indent=2)
674 Log(import_log_msg, indent=2)
675 Log("remove export", indent=2)
676 self.ExecOrQueue(instance, exp_op, rem_op, imp_op, erem_op)
678 def StopInstanceOp(self, instance):
679 """Stop given instance."""
680 return opcodes.OpShutdownInstance(instance_name=instance)
682 def StartInstanceOp(self, instance):
683 """Start given instance."""
684 return opcodes.OpStartupInstance(instance_name=instance, force=False)
686 def RenameInstanceOp(self, instance, instance_new):
687 """Rename instance."""
688 return opcodes.OpRenameInstance(instance_name=instance,
689 new_name=instance_new)
693 def BurnStopStart(self):
694 """Stop/start the instances."""
695 Log("Stopping and starting instances")
696 for instance in self.instances:
697 Log("instance %s" % instance, indent=1)
698 op1 = self.StopInstanceOp(instance)
699 op2 = self.StartInstanceOp(instance)
700 self.ExecOrQueue(instance, op1, op2)
703 def BurnRemove(self):
704 """Remove the instances."""
705 Log("Removing instances")
706 for instance in self.to_rem:
707 Log("instance %s" % instance, indent=1)
708 op = opcodes.OpRemoveInstance(instance_name=instance,
709 ignore_failures=True)
710 self.ExecOrQueue(instance, op)
712 def BurnRename(self):
713 """Rename the instances.
715 Note that this function will not execute in parallel, since we
716 only have one target for rename.
719 Log("Renaming instances")
720 rename = self.opts.rename
721 for instance in self.instances:
722 Log("instance %s" % instance, indent=1)
723 op_stop1 = self.StopInstanceOp(instance)
724 op_stop2 = self.StopInstanceOp(rename)
725 op_rename1 = self.RenameInstanceOp(instance, rename)
726 op_rename2 = self.RenameInstanceOp(rename, instance)
727 op_start1 = self.StartInstanceOp(rename)
728 op_start2 = self.StartInstanceOp(instance)
729 self.ExecOp(False, op_stop1, op_rename1, op_start1)
730 self._CheckInstanceAlive(rename)
731 self.ExecOp(False, op_stop2, op_rename2, op_start2)
732 self._CheckInstanceAlive(instance)
736 def BurnReinstall(self):
737 """Reinstall the instances."""
738 Log("Reinstalling instances")
739 for instance in self.instances:
740 Log("instance %s" % instance, indent=1)
741 op1 = self.StopInstanceOp(instance)
742 op2 = opcodes.OpReinstallInstance(instance_name=instance)
743 Log("reinstall without passing the OS", indent=2)
744 op3 = opcodes.OpReinstallInstance(instance_name=instance,
745 os_type=self.opts.os)
746 Log("reinstall specifying the OS", indent=2)
747 op4 = self.StartInstanceOp(instance)
748 self.ExecOrQueue(instance, op1, op2, op3, op4)
752 def BurnReboot(self):
753 """Reboot the instances."""
754 Log("Rebooting instances")
755 for instance in self.instances:
756 Log("instance %s" % instance, indent=1)
758 for reboot_type in constants.REBOOT_TYPES:
759 op = opcodes.OpRebootInstance(instance_name=instance,
760 reboot_type=reboot_type,
761 ignore_secondaries=False)
762 Log("reboot with type '%s'" % reboot_type, indent=2)
764 self.ExecOrQueue(instance, *ops) # pylint: disable-msg=W0142
768 def BurnActivateDisks(self):
769 """Activate and deactivate disks of the instances."""
770 Log("Activating/deactivating disks")
771 for instance in self.instances:
772 Log("instance %s" % instance, indent=1)
773 op_start = self.StartInstanceOp(instance)
774 op_act = opcodes.OpActivateInstanceDisks(instance_name=instance)
775 op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance)
776 op_stop = self.StopInstanceOp(instance)
777 Log("activate disks when online", indent=2)
778 Log("activate disks when offline", indent=2)
779 Log("deactivate disks (when offline)", indent=2)
780 self.ExecOrQueue(instance, op_act, op_stop, op_act, op_deact, op_start)
784 def BurnAddRemoveDisks(self):
785 """Add and remove an extra disk for the instances."""
786 Log("Adding and removing disks")
787 for instance in self.instances:
788 Log("instance %s" % instance, indent=1)
789 op_add = opcodes.OpSetInstanceParams(\
790 instance_name=instance,
791 disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})])
792 op_rem = opcodes.OpSetInstanceParams(\
793 instance_name=instance, disks=[(constants.DDM_REMOVE, {})])
794 op_stop = self.StopInstanceOp(instance)
795 op_start = self.StartInstanceOp(instance)
796 Log("adding a disk", indent=2)
797 Log("removing last disk", indent=2)
798 self.ExecOrQueue(instance, op_add, op_stop, op_rem, op_start)
801 def BurnAddRemoveNICs(self):
802 """Add and remove an extra NIC for the instances."""
803 Log("Adding and removing NICs")
804 for instance in self.instances:
805 Log("instance %s" % instance, indent=1)
806 op_add = opcodes.OpSetInstanceParams(\
807 instance_name=instance, nics=[(constants.DDM_ADD, {})])
808 op_rem = opcodes.OpSetInstanceParams(\
809 instance_name=instance, nics=[(constants.DDM_REMOVE, {})])
810 Log("adding a NIC", indent=2)
811 Log("removing last NIC", indent=2)
812 self.ExecOrQueue(instance, op_add, op_rem)
814 def _CheckInstanceAlive(self, instance):
815 """Check if an instance is alive by doing http checks.
817 This will try to retrieve the url on the instance /hostname.txt
818 and check that it contains the hostname of the instance. In case
819 we get ECONNREFUSED, we retry up to the net timeout seconds, for
820 any other error we abort.
823 if not self.opts.http_check:
825 end_time = time.time() + self.opts.net_timeout
827 while time.time() < end_time and url is None:
829 url = self.url_opener.open("http://%s/hostname.txt" % instance)
831 # here we can have connection refused, no route to host, etc.
834 raise InstanceDown(instance, "Cannot contact instance")
835 hostname = url.read().strip()
837 if hostname != instance:
838 raise InstanceDown(instance, ("Hostname mismatch, expected %s, got %s" %
839 (instance, hostname)))
841 def BurninCluster(self):
842 """Test a cluster intensively.
844 This will create instances and then start/stop/failover them.
845 It is safe for existing instances but could impact performance.
851 Log("Testing global parameters")
853 if (len(self.nodes) == 1 and
854 opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
856 Err("When one node is available/selected the disk template must"
857 " be 'diskless', 'file' or 'plain'")
861 self.BurnCreateInstances()
862 if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
863 self.BurnReplaceDisks1D8()
864 if (opts.do_replace2 and len(self.nodes) > 2 and
865 opts.disk_template in constants.DTS_NET_MIRROR) :
866 self.BurnReplaceDisks2()
868 if (opts.disk_template != constants.DT_DISKLESS and
869 utils.any(self.disk_growth, lambda n: n > 0)):
872 if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
875 if opts.do_migrate and opts.disk_template == constants.DT_DRBD8:
878 if (opts.do_move and len(self.nodes) > 1 and
879 opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]):
882 if (opts.do_importexport and
883 opts.disk_template not in (constants.DT_DISKLESS,
885 self.BurnImportExport()
887 if opts.do_reinstall:
893 if opts.do_addremove_disks:
894 self.BurnAddRemoveDisks()
896 if opts.do_addremove_nics:
897 self.BurnAddRemoveNICs()
899 if opts.do_activate_disks:
900 self.BurnActivateDisks()
905 if opts.do_startstop:
911 Log("Error detected: opcode buffer follows:\n\n")
912 Log(self.GetFeedbackBuf())
914 if not self.opts.keep_instances:
917 except Exception, err: # pylint: disable-msg=W0703
918 if has_err: # already detected errors, so errors in removal
920 Log("Note: error detected during instance remove: %s" % str(err))
921 else: # non-expected error
931 return burner.BurninCluster()
934 if __name__ == "__main__":