X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/454723b56206234cd2903594e9354f72712c70a1..43c16a8a1adfd543751fcaf60ad4c8e04cf83688:/tools/burnin diff --git a/tools/burnin b/tools/burnin index a1dfc91..fec9a04 100755 --- a/tools/burnin +++ b/tools/burnin @@ -1,7 +1,7 @@ #!/usr/bin/python # -# Copyright (C) 2006, 2007 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -36,6 +36,8 @@ from ganeti import constants from ganeti import cli from ganeti import errors from ganeti import utils +from ganeti import hypervisor +from ganeti import compat from ganeti.confd import client as confd_client @@ -49,6 +51,7 @@ LOG_HEADERS = { 2: "" } + class InstanceDown(Exception): """The checked instance was not up""" @@ -71,8 +74,8 @@ def Log(msg, *args, **kwargs): """ if args: msg = msg % args - indent = kwargs.get('indent', 0) - sys.stdout.write("%*s%s%s\n" % (2*indent, "", + indent = kwargs.get("indent", 0) + sys.stdout.write("%*s%s%s\n" % (2 * indent, "", LOG_HEADERS.get(indent, " "), msg)) sys.stdout.flush() @@ -88,12 +91,12 @@ def Err(msg, exit_code=1): class SimpleOpener(urllib.FancyURLopener): """A simple url opener""" - # pylint: disable-msg=W0221 + # pylint: disable=W0221 def prompt_user_passwd(self, host, realm, clear_cache=0): """No-interaction version of prompt_user_passwd.""" # we follow parent class' API - # pylint: disable-msg=W0613 + # pylint: disable=W0613 return None, None def http_error_default(self, url, fp, errcode, errmsg, headers): @@ -111,6 +114,8 @@ OPTIONS = [ help="OS to use during burnin", metavar="", completion_suggest=cli.OPT_COMPL_ONE_OS), + cli.HYPERVISOR_OPT, + cli.OSPARAMS_OPT, cli.cli_option("--disk-size", dest="disk_size", help="Disk size (determines disk count)", default="128m", type="string", metavar="", @@ -119,9 +124,20 @@ OPTIONS = [ cli.cli_option("--disk-growth", dest="disk_growth", help="Disk growth", default="128m", type="string", metavar=""), cli.cli_option("--mem-size", dest="mem_size", help="Memory size", + default=None, type="unit", metavar="", + completion_suggest=("128M 256M 512M 1G 4G 8G" + " 12G 16G").split()), + cli.cli_option("--maxmem-size", dest="maxmem_size", help="Max Memory size", + default=256, type="unit", metavar="", + completion_suggest=("128M 256M 512M 1G 4G 8G" + " 12G 16G").split()), + cli.cli_option("--minmem-size", dest="minmem_size", help="Min Memory size", default=128, type="unit", metavar="", completion_suggest=("128M 256M 512M 1G 4G 8G" " 12G 16G").split()), + cli.cli_option("--vcpu-count", dest="vcpu_count", help="VCPU count", + default=3, type="unit", metavar="", + completion_suggest=("1 2 3 4").split()), cli.DEBUG_OPT, cli.VERBOSE_OPT, cli.NOIPCHECK_OPT, @@ -154,6 +170,8 @@ OPTIONS = [ cli.cli_option("--no-reboot", dest="do_reboot", help="Skip instance reboot", action="store_false", default=True), + cli.cli_option("--reboot-types", dest="reboot_types", + help="Specify the reboot types", default=None), cli.cli_option("--no-activate-disks", dest="do_activate_disks", help="Skip disk activation/deactivation", action="store_false", default=True), @@ -168,7 +186,7 @@ OPTIONS = [ const=[], default=[{}]), cli.cli_option("--no-confd", dest="do_confd_tests", help="Skip confd queries", - action="store_false", default=True), + action="store_false", default=constants.ENABLE_CONFD), cli.cli_option("--rename", dest="rename", default=None, help=("Give one unused instance name which is taken" " to start the renaming sequence"), @@ -176,7 +194,8 @@ OPTIONS = [ cli.cli_option("-t", "--disk-template", dest="disk_template", choices=list(constants.DISK_TEMPLATES), default=constants.DT_DRBD8, - help="Disk template (diskless, file, plain or drbd) [drbd]"), + help="Disk template (diskless, file, plain, sharedfile" + " or drbd) [drbd]"), cli.cli_option("-n", "--nodes", dest="nodes", default="", help=("Comma separated list of nodes to perform" " the burnin on (defaults to all nodes)"), @@ -221,7 +240,7 @@ def _DoCheckInstances(fn): def wrapper(self, *args, **kwargs): val = fn(self, *args, **kwargs) for instance in self.instances: - self._CheckInstanceAlive(instance) # pylint: disable-msg=W0212 + self._CheckInstanceAlive(instance) # pylint: disable=W0212 return val return wrapper @@ -252,7 +271,6 @@ class Burner(object): def __init__(self): """Constructor.""" - utils.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True) self.url_opener = SimpleOpener() self._feed_buf = StringIO() self.nodes = [] @@ -302,7 +320,7 @@ class Burner(object): Log("Idempotent %s succeeded after %d retries", msg, MAX_RETRIES - retry_count) return val - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 if retry_count == 0: Log("Non-idempotent %s failed, aborting", msg) raise @@ -314,11 +332,6 @@ class Burner(object): msg, MAX_RETRIES - retry_count + 1, MAX_RETRIES, err) self.MaybeRetry(retry_count - 1, msg, fn, *args) - def _SetDebug(self, ops): - """Set the debug value on the given opcodes""" - for op in ops: - op.debug_level = self.opts.debug - def _ExecOp(self, *ops): """Execute one or more opcodes and manage the exec buffer. @@ -344,16 +357,16 @@ class Burner(object): rval = MAX_RETRIES else: rval = 0 - self._SetDebug(ops) + cli.SetGenericOpcodeOpts(ops, self.opts) return self.MaybeRetry(rval, "opcode", self._ExecOp, *ops) def ExecOrQueue(self, name, ops, post_process=None): """Execute an opcode and manage the exec buffer.""" if self.opts.parallel: - self._SetDebug(ops) + cli.SetGenericOpcodeOpts(ops, self.opts) self.queued_ops.append((ops, name, post_process)) else: - val = self.ExecOp(self.queue_retry, *ops) # pylint: disable-msg=W0142 + val = self.ExecOp(self.queue_retry, *ops) # pylint: disable=W0142 if post_process is not None: post_process() return val @@ -369,7 +382,7 @@ class Burner(object): def CommitQueue(self): """Execute all submitted opcodes in case of parallel burnin""" - if not self.opts.parallel: + if not self.opts.parallel or not self.queued_ops: return if self.queue_retry: @@ -395,10 +408,10 @@ class Burner(object): self.ClearFeedbackBuf() jex = cli.JobExecutor(cl=self.cl, feedback_fn=self.Feedback) for ops, name, _ in jobs: - jex.QueueJob(name, *ops) # pylint: disable-msg=W0142 + jex.QueueJob(name, *ops) # pylint: disable=W0142 try: results = jex.GetResults() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 Log("Jobs failed: %s", err) raise BurninFailure() @@ -409,7 +422,7 @@ class Burner(object): if post_process: try: post_process() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 Log("Post process call for job %s failed: %s", name, err) fail = True val.append(result) @@ -437,10 +450,19 @@ class Burner(object): if len(args) < 1 or options.os is None: Usage() + if options.mem_size: + options.maxmem_size = options.mem_size + options.minmem_size = options.mem_size + elif options.minmem_size > options.maxmem_size: + Err("Maximum memory lower than minimum memory") + supported_disk_templates = (constants.DT_DISKLESS, constants.DT_FILE, + constants.DT_SHARED_FILE, constants.DT_PLAIN, - constants.DT_DRBD8) + constants.DT_DRBD8, + constants.DT_RBD, + ) if options.disk_template not in supported_disk_templates: Err("Unknown disk template '%s'" % options.disk_template) @@ -470,10 +492,23 @@ class Burner(object): self.opts = options self.instances = args self.bep = { - constants.BE_MEMORY: options.mem_size, - constants.BE_VCPUS: 1, + constants.BE_MINMEM: options.minmem_size, + constants.BE_MAXMEM: options.maxmem_size, + constants.BE_VCPUS: options.vcpu_count, } + + self.hypervisor = None self.hvp = {} + if options.hypervisor: + self.hypervisor, self.hvp = options.hypervisor + + if options.reboot_types is None: + options.reboot_types = constants.REBOOT_TYPES + else: + options.reboot_types = options.reboot_types.split(",") + rt_diff = set(options.reboot_types).difference(constants.REBOOT_TYPES) + if rt_diff: + Err("Invalid reboot types specified: %s" % utils.CommaJoin(rt_diff)) socket.setdefaulttimeout(options.net_timeout) @@ -484,24 +519,26 @@ class Burner(object): else: names = [] try: - op = opcodes.OpQueryNodes(output_fields=["name", "offline", "drained"], - names=names, use_locking=True) + op = opcodes.OpNodeQuery(output_fields=["name", "offline", "drained"], + names=names, use_locking=True) result = self.ExecOp(True, op) except errors.GenericError, err: err_code, msg = cli.FormatError(err) Err(msg, exit_code=err_code) self.nodes = [data[0] for data in result if not (data[1] or data[2])] - op_diagnose = opcodes.OpDiagnoseOS(output_fields=["name", "valid", - "variants"], names=[]) + op_diagnose = opcodes.OpOsDiagnose(output_fields=["name", + "variants", + "hidden"], + names=[]) result = self.ExecOp(True, op_diagnose) if not result: Err("Can't get the OS list") found = False - for (name, valid, variants) in result: - if valid and self.opts.os in cli.CalculateOSNames(name, variants): + for (name, variants, _) in result: + if self.opts.os in cli.CalculateOSNames(name, variants): found = True break @@ -515,6 +552,9 @@ class Burner(object): default_nic_params = self.cluster_info["nicparams"][constants.PP_DEFAULT] self.cluster_default_nicparams = default_nic_params + if self.hypervisor is None: + self.hypervisor = self.cluster_info["default_hypervisor"] + self.hv_class = hypervisor.GetHypervisorClass(self.hypervisor) @_DoCheckInstances @_DoBatch(False) @@ -533,7 +573,7 @@ class Burner(object): if self.opts.iallocator: pnode = snode = None msg = "with iallocator %s" % self.opts.iallocator - elif self.opts.disk_template not in constants.DTS_NET_MIRROR: + elif self.opts.disk_template not in constants.DTS_INT_MIRROR: snode = None msg = "on %s" % pnode else: @@ -541,9 +581,9 @@ class Burner(object): Log(msg, indent=2) - op = opcodes.OpCreateInstance(instance_name=instance, - disks = [ {"size": size} - for size in self.disk_size], + op = opcodes.OpInstanceCreate(instance_name=instance, + disks=[{"size": size} + for size in self.disk_size], disk_template=self.opts.disk_template, nics=self.opts.nics, mode=constants.INSTANCE_CREATE, @@ -559,11 +599,25 @@ class Burner(object): iallocator=self.opts.iallocator, beparams=self.bep, hvparams=self.hvp, + hypervisor=self.hypervisor, + osparams=self.opts.osparams, ) remove_instance = lambda name: lambda: self.to_rem.append(name) self.ExecOrQueue(instance, [op], post_process=remove_instance(instance)) @_DoBatch(False) + def BurnModifyRuntimeMemory(self): + """Alter the runtime memory.""" + Log("Setting instance runtime memory") + for instance in self.instances: + Log("instance %s", instance, indent=1) + tgt_mem = self.bep[constants.BE_MINMEM] + op = opcodes.OpInstanceSetParams(instance_name=instance, + runtime_mem=tgt_mem) + Log("Set memory to %s MB", tgt_mem, indent=2) + self.ExecOrQueue(instance, [op]) + + @_DoBatch(False) def BurnGrowDisks(self): """Grow both the os and the swap disks by the requested amount, if any.""" Log("Growing disks") @@ -571,8 +625,8 @@ class Burner(object): Log("instance %s", instance, indent=1) for idx, growth in enumerate(self.disk_growth): if growth > 0: - op = opcodes.OpGrowDisk(instance_name=instance, disk=idx, - amount=growth, wait_for_sync=True) + op = opcodes.OpInstanceGrowDisk(instance_name=instance, disk=idx, + amount=growth, wait_for_sync=True) Log("increase disk/%s by %s MB", idx, growth, indent=2) self.ExecOrQueue(instance, [op]) @@ -580,14 +634,15 @@ class Burner(object): def BurnReplaceDisks1D8(self): """Replace disks on primary and secondary for drbd8.""" Log("Replacing disks on the same nodes") + early_release = self.opts.early_release for instance in self.instances: Log("instance %s", instance, indent=1) ops = [] for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI: - op = opcodes.OpReplaceDisks(instance_name=instance, - mode=mode, - disks=[i for i in range(self.disk_count)], - early_release=self.opts.early_release) + op = opcodes.OpInstanceReplaceDisks(instance_name=instance, + mode=mode, + disks=list(range(self.disk_count)), + early_release=early_release) Log("run %s", mode, indent=2) ops.append(op) self.ExecOrQueue(instance, ops) @@ -607,12 +662,12 @@ class Burner(object): msg = "with iallocator %s" % self.opts.iallocator else: msg = tnode - op = opcodes.OpReplaceDisks(instance_name=instance, - mode=mode, - remote_node=tnode, - iallocator=self.opts.iallocator, - disks=[], - early_release=self.opts.early_release) + op = opcodes.OpInstanceReplaceDisks(instance_name=instance, + mode=mode, + remote_node=tnode, + iallocator=self.opts.iallocator, + disks=[], + early_release=self.opts.early_release) Log("run %s %s", mode, msg, indent=2) self.ExecOrQueue(instance, [op]) @@ -623,7 +678,7 @@ class Burner(object): Log("Failing over instances") for instance in self.instances: Log("instance %s", instance, indent=1) - op = opcodes.OpFailoverInstance(instance_name=instance, + op = opcodes.OpInstanceFailover(instance_name=instance, ignore_consistency=False) self.ExecOrQueue(instance, [op]) @@ -636,7 +691,7 @@ class Burner(object): self.instances) for tnode, instance in mytor: Log("instance %s", instance, indent=1) - op = opcodes.OpMoveInstance(instance_name=instance, + op = opcodes.OpInstanceMove(instance_name=instance, target_node=tnode) self.ExecOrQueue(instance, [op]) @@ -646,10 +701,10 @@ class Burner(object): Log("Migrating instances") for instance in self.instances: Log("instance %s", instance, indent=1) - op1 = opcodes.OpMigrateInstance(instance_name=instance, live=True, + op1 = opcodes.OpInstanceMigrate(instance_name=instance, mode=None, cleanup=False) - op2 = opcodes.OpMigrateInstance(instance_name=instance, live=True, + op2 = opcodes.OpInstanceMigrate(instance_name=instance, mode=None, cleanup=True) Log("migration and migration cleanup", indent=2) self.ExecOrQueue(instance, [op1, op2]) @@ -669,8 +724,8 @@ class Burner(object): for pnode, snode, enode, instance in mytor: Log("instance %s", instance, indent=1) # read the full name of the instance - nam_op = opcodes.OpQueryInstances(output_fields=["name"], - names=[instance], use_locking=True) + nam_op = opcodes.OpInstanceQuery(output_fields=["name"], + names=[instance], use_locking=True) full_name = self.ExecOp(False, nam_op)[0][0] if self.opts.iallocator: @@ -678,7 +733,7 @@ class Burner(object): import_log_msg = ("import from %s" " with iallocator %s" % (enode, self.opts.iallocator)) - elif self.opts.disk_template not in constants.DTS_NET_MIRROR: + elif self.opts.disk_template not in constants.DTS_INT_MIRROR: snode = None import_log_msg = ("import from %s to %s" % (enode, pnode)) @@ -686,15 +741,16 @@ class Burner(object): import_log_msg = ("import from %s to %s, %s" % (enode, pnode, snode)) - exp_op = opcodes.OpExportInstance(instance_name=instance, - target_node=enode, - shutdown=True) - rem_op = opcodes.OpRemoveInstance(instance_name=instance, + exp_op = opcodes.OpBackupExport(instance_name=instance, + target_node=enode, + mode=constants.EXPORT_MODE_LOCAL, + shutdown=True) + rem_op = opcodes.OpInstanceRemove(instance_name=instance, ignore_failures=True) imp_dir = utils.PathJoin(constants.EXPORT_DIR, full_name) - imp_op = opcodes.OpCreateInstance(instance_name=instance, - disks = [ {"size": size} - for size in self.disk_size], + imp_op = opcodes.OpInstanceCreate(instance_name=instance, + disks=[{"size": size} + for size in self.disk_size], disk_template=self.opts.disk_template, nics=self.opts.nics, mode=constants.INSTANCE_IMPORT, @@ -711,9 +767,10 @@ class Burner(object): iallocator=self.opts.iallocator, beparams=self.bep, hvparams=self.hvp, + osparams=self.opts.osparams, ) - erem_op = opcodes.OpRemoveExport(instance_name=instance) + erem_op = opcodes.OpBackupRemove(instance_name=instance) Log("export to node %s", enode, indent=2) Log("remove instance", indent=2) @@ -724,17 +781,17 @@ class Burner(object): @staticmethod def StopInstanceOp(instance): """Stop given instance.""" - return opcodes.OpShutdownInstance(instance_name=instance) + return opcodes.OpInstanceShutdown(instance_name=instance) @staticmethod def StartInstanceOp(instance): """Start given instance.""" - return opcodes.OpStartupInstance(instance_name=instance, force=False) + return opcodes.OpInstanceStartup(instance_name=instance, force=False) @staticmethod def RenameInstanceOp(instance, instance_new): """Rename instance.""" - return opcodes.OpRenameInstance(instance_name=instance, + return opcodes.OpInstanceRename(instance_name=instance, new_name=instance_new) @_DoCheckInstances @@ -754,7 +811,7 @@ class Burner(object): Log("Removing instances") for instance in self.to_rem: Log("instance %s", instance, indent=1) - op = opcodes.OpRemoveInstance(instance_name=instance, + op = opcodes.OpInstanceRemove(instance_name=instance, ignore_failures=True) self.ExecOrQueue(instance, [op]) @@ -788,9 +845,9 @@ class Burner(object): for instance in self.instances: Log("instance %s", instance, indent=1) op1 = self.StopInstanceOp(instance) - op2 = opcodes.OpReinstallInstance(instance_name=instance) + op2 = opcodes.OpInstanceReinstall(instance_name=instance) Log("reinstall without passing the OS", indent=2) - op3 = opcodes.OpReinstallInstance(instance_name=instance, + op3 = opcodes.OpInstanceReinstall(instance_name=instance, os_type=self.opts.os) Log("reinstall specifying the OS", indent=2) op4 = self.StartInstanceOp(instance) @@ -804,8 +861,8 @@ class Burner(object): for instance in self.instances: Log("instance %s", instance, indent=1) ops = [] - for reboot_type in constants.REBOOT_TYPES: - op = opcodes.OpRebootInstance(instance_name=instance, + for reboot_type in self.opts.reboot_types: + op = opcodes.OpInstanceReboot(instance_name=instance, reboot_type=reboot_type, ignore_secondaries=False) Log("reboot with type '%s'", reboot_type, indent=2) @@ -820,8 +877,8 @@ class Burner(object): for instance in self.instances: Log("instance %s", instance, indent=1) op_start = self.StartInstanceOp(instance) - op_act = opcodes.OpActivateInstanceDisks(instance_name=instance) - op_deact = opcodes.OpDeactivateInstanceDisks(instance_name=instance) + op_act = opcodes.OpInstanceActivateDisks(instance_name=instance) + op_deact = opcodes.OpInstanceDeactivateDisks(instance_name=instance) op_stop = self.StopInstanceOp(instance) Log("activate disks when online", indent=2) Log("activate disks when offline", indent=2) @@ -835,10 +892,10 @@ class Burner(object): Log("Adding and removing disks") for instance in self.instances: Log("instance %s", instance, indent=1) - op_add = opcodes.OpSetInstanceParams(\ + op_add = opcodes.OpInstanceSetParams(\ instance_name=instance, disks=[(constants.DDM_ADD, {"size": self.disk_size[0]})]) - op_rem = opcodes.OpSetInstanceParams(\ + op_rem = opcodes.OpInstanceSetParams(\ instance_name=instance, disks=[(constants.DDM_REMOVE, {})]) op_stop = self.StopInstanceOp(instance) op_start = self.StartInstanceOp(instance) @@ -852,9 +909,9 @@ class Burner(object): Log("Adding and removing NICs") for instance in self.instances: Log("instance %s", instance, indent=1) - op_add = opcodes.OpSetInstanceParams(\ + op_add = opcodes.OpInstanceSetParams(\ instance_name=instance, nics=[(constants.DDM_ADD, {})]) - op_rem = opcodes.OpSetInstanceParams(\ + op_rem = opcodes.OpInstanceSetParams(\ instance_name=instance, nics=[(constants.DDM_REMOVE, {})]) Log("adding a NIC", indent=2) Log("removing last NIC", indent=2) @@ -958,28 +1015,43 @@ class Burner(object): if (len(self.nodes) == 1 and opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN, - constants.DT_FILE)): + constants.DT_FILE, + constants.DT_SHARED_FILE)): Err("When one node is available/selected the disk template must" " be 'diskless', 'file' or 'plain'") + if opts.do_confd_tests and not constants.ENABLE_CONFD: + Err("You selected confd tests but confd was disabled at configure time") + has_err = True try: self.BurnCreateInstances() - if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR: + + if self.bep[constants.BE_MINMEM] < self.bep[constants.BE_MAXMEM]: + self.BurnModifyRuntimeMemory() + + if opts.do_replace1 and opts.disk_template in constants.DTS_INT_MIRROR: self.BurnReplaceDisks1D8() if (opts.do_replace2 and len(self.nodes) > 2 and - opts.disk_template in constants.DTS_NET_MIRROR) : + opts.disk_template in constants.DTS_INT_MIRROR): self.BurnReplaceDisks2() if (opts.disk_template in constants.DTS_GROWABLE and - utils.any(self.disk_growth, lambda n: n > 0)): + compat.any(n > 0 for n in self.disk_growth)): self.BurnGrowDisks() - if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR: + if opts.do_failover and opts.disk_template in constants.DTS_MIRRORED: self.BurnFailover() - if opts.do_migrate and opts.disk_template == constants.DT_DRBD8: - self.BurnMigrate() + if opts.do_migrate: + if opts.disk_template not in constants.DTS_MIRRORED: + Log("Skipping migration (disk template %s does not support it)", + opts.disk_template) + elif not self.hv_class.CAN_MIGRATE: + Log("Skipping migration (hypervisor %s does not support it)", + self.hypervisor) + else: + self.BurnMigrate() if (opts.do_move and len(self.nodes) > 1 and opts.disk_template in [constants.DT_PLAIN, constants.DT_FILE]): @@ -987,6 +1059,7 @@ class Burner(object): if (opts.do_importexport and opts.disk_template not in (constants.DT_DISKLESS, + constants.DT_SHARED_FILE, constants.DT_FILE)): self.BurnImportExport() @@ -1029,21 +1102,24 @@ class Burner(object): if not self.opts.keep_instances: try: self.BurnRemove() - except Exception, err: # pylint: disable-msg=W0703 + except Exception, err: # pylint: disable=W0703 if has_err: # already detected errors, so errors in removal # are quite expected Log("Note: error detected during instance remove: %s", err) else: # non-expected error raise - return 0 + return constants.EXIT_SUCCESS def main(): - """Main function""" + """Main function. + + """ + utils.SetupLogging(constants.LOG_BURNIN, sys.argv[0], + debug=False, stderr_logging=True) - burner = Burner() - return burner.BurninCluster() + return Burner().BurninCluster() if __name__ == "__main__":