X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/21546b1c07cd6e73e9c21be196d5bba494a2e2b6..4fbe765c8abb924646de8e0bc9b979746b22cd15:/tools/burnin diff --git a/tools/burnin b/tools/burnin index d4644a2..f5b9b1a 100755 --- a/tools/burnin +++ b/tools/burnin @@ -1,10 +1,32 @@ #!/usr/bin/python # -"""Burnin program""" +# Copyright (C) 2006, 2007 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + + +"""Burnin program + +""" +import os import sys import optparse +import time from itertools import izip, islice, cycle from cStringIO import StringIO @@ -16,8 +38,10 @@ from ganeti import logger from ganeti import errors from ganeti import utils + USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...") + def Usage(): """Shows program usage information and exits the program.""" @@ -25,20 +49,22 @@ def Usage(): print >> sys.stderr, USAGE sys.exit(2) + def Log(msg): """Simple function that prints out its argument. """ print msg + sys.stdout.flush() + class Burner(object): """Burner class.""" def __init__(self): """Constructor.""" - logger.SetupLogging(debug=False, program="ganeti/burnin") + logger.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True) self._feed_buf = StringIO() - self.proc = mcpu.Processor(feedback=self.Feedback) self.nodes = [] self.instances = [] self.to_rem = [] @@ -56,13 +82,15 @@ class Burner(object): def Feedback(self, msg): """Acumulate feedback in our buffer.""" - self._feed_buf.write(msg) - self._feed_buf.write("\n") + self._feed_buf.write("%s %s\n" % (time.ctime(utils.MergeTime(msg[0])), + msg[2])) + if self.opts.verbose: + Log(msg) def ExecOp(self, op): """Execute an opcode and manage the exec buffer.""" self.ClearFeedbackBuf() - return self.proc.ExecOpCode(op) + return cli.SubmitOpCode(op, feedback_fn=self.Feedback) def ParseOptions(self): """Parses the command line options. @@ -84,6 +112,8 @@ class Burner(object): default=4 * 1024, type="unit", metavar="") parser.add_option("--swap-size", dest="swap_size", help="Swap size", default=4 * 1024, type="unit", metavar="") + parser.add_option("--mem-size", dest="mem_size", help="Memory size", + default=128, type="unit", metavar="") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="print command execution messages to stdout") @@ -96,30 +126,46 @@ class Burner(object): parser.add_option("--no-failover", dest="do_failover", help="Skip instance failovers", action="store_false", default=True) + parser.add_option("--no-importexport", dest="do_importexport", + help="Skip instance export/import", action="store_false", + default=True) + parser.add_option("--no-startstop", dest="do_startstop", + help="Skip instance stop/start", action="store_false", + default=True) + parser.add_option("--rename", dest="rename", default=None, + help="Give one unused instance name which is taken" + " to start the renaming sequence", + metavar="") parser.add_option("-t", "--disk-template", dest="disk_template", - choices=("remote_raid1", "drbd8"), - default="remote_raid1", - help="Template type for network mirroring (remote_raid1" - " or drbd8) [remote_raid1]") + choices=("diskless", "file", "plain", "drbd"), + default="drbd", + help="Disk template (diskless, file, plain or drbd)" + " [drbd]") parser.add_option("-n", "--nodes", dest="nodes", default="", help="Comma separated list of nodes to perform" " the burnin on (defaults to all nodes)") + parser.add_option("--iallocator", dest="iallocator", + default=None, type="string", + help="Perform the allocation using an iallocator" + " instead of fixed node spread (node restrictions no" + " longer apply, therefore -n/--nodes must not be used") options, args = parser.parse_args() if len(args) < 1 or options.os is None: Usage() - if options.disk_template == "plain": - disk_template = constants.DT_PLAIN - elif options.disk_template == "remote_raid1": - disk_template = constants.DT_REMOTE_RAID1 - elif options.disk_template == "drbd8": - disk_template = constants.DT_DRBD8 - else: + supported_disk_templates = (constants.DT_DISKLESS, + constants.DT_FILE, + constants.DT_PLAIN, + constants.DT_DRBD8) + if options.disk_template not in supported_disk_templates: Log("Unknown disk template '%s'" % options.disk_template) sys.exit(1) - options.disk_template = disk_template + if options.nodes and options.iallocator: + Log("Give either the nodes option or the iallocator option, not both") + sys.exit(1) + self.opts = options self.instances = args @@ -138,22 +184,15 @@ class Burner(object): sys.exit(err_code) self.nodes = [data[0] for data in result] - result = self.ExecOp(opcodes.OpDiagnoseOS()) + result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"], + names=[])) if not result: Log("Can't get the OS list") sys.exit(1) # filter non-valid OS-es - oses = {} - for node_name in result: - oses[node_name] = [obj for obj in result[node_name] if obj] - - fnode = oses.keys()[0] - os_set = set([os_inst.name for os_inst in oses[fnode]]) - del oses[fnode] - for node in oses: - os_set &= set([os_inst.name for os_inst in oses[node]]) + os_set = [val[0] for val in result if val[1]] if self.opts.os not in os_set: Log("OS '%s' not found" % self.opts.os) @@ -168,8 +207,18 @@ class Burner(object): islice(cycle(self.nodes), 1, None), self.instances) for pnode, snode, instance in mytor: + if self.opts.iallocator: + pnode = snode = None + Log("- Add instance %s (iallocator: %s)" % + (instance, self.opts.iallocator)) + elif self.opts.disk_template not in constants.DTS_NET_MIRROR: + snode = None + Log("- Add instance %s on node %s" % (instance, pnode)) + else: + Log("- Add instance %s on nodes %s/%s" % (instance, pnode, snode)) + op = opcodes.OpCreateInstance(instance_name=instance, - mem_size=128, + mem_size=self.opts.mem_size, disk_size=self.opts.os_size, swap_size=self.opts.swap_size, disk_template=self.opts.disk_template, @@ -180,22 +229,19 @@ class Burner(object): vcpus=1, start=True, ip_check=True, - wait_for_sync=True) - Log("- Add instance %s on node %s" % (instance, pnode)) - self.ExecOp(op) - self.to_rem.append(instance) - - def ReplaceDisks1R1(self): - """Replace disks with the same secondary for rr1.""" - # replace all, both disks - for instance in self.instances: - op = opcodes.OpReplaceDisks(instance_name=instance, - remote_node=None, - mode=constants.REPLACE_DISK_ALL, - disks=["sda", "sdb"]) + wait_for_sync=True, + mac="auto", + kernel_path=None, + initrd_path=None, + hvm_boot_order=None, + file_driver="loop", + file_storage_dir=None, + iallocator=self.opts.iallocator, + hvm_nic_type=constants.HT_HVM_NIC_RTL8139, + hvm_disk_type=constants.HT_HVM_DEV_IOEMU) - Log("- Replace disks for instance %s" % (instance)) self.ExecOp(op) + self.to_rem.append(instance) def ReplaceDisks1D8(self): """Replace disks on primary and secondary for drbd8.""" @@ -209,17 +255,17 @@ class Burner(object): def ReplaceDisks2(self): """Replace secondary node.""" - if self.opts.disk_template == constants.DT_REMOTE_RAID1: - mode = constants.REPLACE_DISK_ALL - else: - mode = constants.REPLACE_DISK_SEC + mode = constants.REPLACE_DISK_SEC mytor = izip(islice(cycle(self.nodes), 2, None), self.instances) for tnode, instance in mytor: + if self.opts.iallocator: + tnode = None op = opcodes.OpReplaceDisks(instance_name=instance, mode=mode, remote_node=tnode, + iallocator=self.opts.iallocator, disks=["sda", "sdb"]) Log("- Replace secondary (%s) for instance %s" % (mode, instance)) self.ExecOp(op) @@ -234,23 +280,121 @@ class Burner(object): Log("- Failover instance %s" % (instance)) self.ExecOp(op) + def ImportExport(self): + """Export the instance, delete it, and import it back. + + """ + + mytor = izip(cycle(self.nodes), + islice(cycle(self.nodes), 1, None), + islice(cycle(self.nodes), 2, None), + self.instances) + + for pnode, snode, enode, instance in mytor: + + if self.opts.iallocator: + pnode = snode = None + import_log_msg = ("- Import instance %s from node %s (iallocator: %s)" % + (instance, enode, self.opts.iallocator)) + elif self.opts.disk_template not in constants.DTS_NET_MIRROR: + snode = None + import_log_msg = ("- Import instance %s from node %s to node %s" % + (instance, enode, pnode)) + else: + import_log_msg = ("- Import instance %s from node %s to nodes %s/%s" % + (instance, enode, pnode, snode)) + + exp_op = opcodes.OpExportInstance(instance_name=instance, + target_node=enode, + shutdown=True) + rem_op = opcodes.OpRemoveInstance(instance_name=instance, + ignore_failures=True) + nam_op = opcodes.OpQueryInstances(output_fields=["name"], + names=[instance]) + full_name = self.ExecOp(nam_op)[0][0] + imp_dir = os.path.join(constants.EXPORT_DIR, full_name) + imp_op = opcodes.OpCreateInstance(instance_name=instance, + mem_size=128, + disk_size=self.opts.os_size, + swap_size=self.opts.swap_size, + disk_template=self.opts.disk_template, + mode=constants.INSTANCE_IMPORT, + src_node=enode, + src_path=imp_dir, + pnode=pnode, + snode=snode, + vcpus=1, + start=True, + ip_check=True, + wait_for_sync=True, + mac="auto", + file_storage_dir=None, + file_driver=None, + iallocator=self.opts.iallocator, + hvm_nic_type= + constants.HT_HVM_NIC_RTL8139, + hvm_disk_type= + constants.HT_HVM_DEV_IOEMU) + + erem_op = opcodes.OpRemoveExport(instance_name=instance) + + Log("- Export instance %s to node %s" % (instance, enode)) + self.ExecOp(exp_op) + Log("- Remove instance %s" % (instance)) + self.ExecOp(rem_op) + self.to_rem.remove(instance) + Log(import_log_msg) + self.ExecOp(imp_op) + Log("- Remove export of instance %s" % (instance)) + self.ExecOp(erem_op) + + self.to_rem.append(instance) + + def StopInstance(self, instance): + """Stop given instance.""" + op = opcodes.OpShutdownInstance(instance_name=instance) + Log("- Shutdown instance %s" % instance) + self.ExecOp(op) + + def StartInstance(self, instance): + """Start given instance.""" + op = opcodes.OpStartupInstance(instance_name=instance, force=False) + Log("- Start instance %s" % instance) + self.ExecOp(op) + + def RenameInstance(self, instance, instance_new): + """Rename instance.""" + op = opcodes.OpRenameInstance(instance_name=instance, + new_name=instance_new) + Log("- Rename instance %s to %s" % (instance, instance_new)) + self.ExecOp(op) + def StopStart(self): """Stop/start the instances.""" for instance in self.instances: - op = opcodes.OpShutdownInstance(instance_name=instance) - Log("- Shutdown instance %s" % instance) - self.ExecOp(op) - op = opcodes.OpStartupInstance(instance_name=instance, force=False) - Log("- Start instance %s" % instance) - self.ExecOp(op) + self.StopInstance(instance) + self.StartInstance(instance) def Remove(self): """Remove the instances.""" for instance in self.to_rem: - op = opcodes.OpRemoveInstance(instance_name=instance) + op = opcodes.OpRemoveInstance(instance_name=instance, + ignore_failures=True) Log("- Remove instance %s" % instance) self.ExecOp(op) + + def Rename(self): + """Rename the instances.""" + rename = self.opts.rename + for instance in self.instances: + self.StopInstance(instance) + self.RenameInstance(instance, rename) + self.StartInstance(rename) + self.StopInstance(rename) + self.RenameInstance(rename, instance) + self.StartInstance(instance) + def BurninCluster(self): """Test a cluster intensively. @@ -263,19 +407,18 @@ class Burner(object): Log("- Testing global parameters") - if len(self.nodes) == 1 and opts.disk_template != constants.DT_PLAIN: + if (len(self.nodes) == 1 and + opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN, + constants.DT_FILE)): Log("When one node is available/selected the disk template must" - " be 'plain'") + " be 'diskless', 'file' or 'plain'") sys.exit(1) has_err = True try: self.CreateInstances() if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR: - if opts.disk_template == constants.DT_REMOTE_RAID1: - self.ReplaceDisks1R1() - elif opts.disk_template == constants.DT_DRBD8: - self.ReplaceDisks1D8() + self.ReplaceDisks1D8() if (opts.do_replace2 and len(self.nodes) > 2 and opts.disk_template in constants.DTS_NET_MIRROR) : self.ReplaceDisks2() @@ -283,7 +426,15 @@ class Burner(object): if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR: self.Failover() - self.StopStart() + if opts.do_importexport: + self.ImportExport() + + if opts.do_startstop: + self.StopStart() + + if opts.rename: + self.Rename() + has_err = False finally: if has_err: @@ -294,21 +445,13 @@ class Burner(object): return 0 + def main(): """Main function""" burner = Burner() - try: - utils.Lock('cmd', max_retries=15, debug=True) - except errors.LockError, err: - logger.ToStderr(str(err)) - return 1 - try: - retval = burner.BurninCluster() - finally: - utils.Unlock('cmd') - utils.LockCleanup() - return retval + return burner.BurninCluster() + if __name__ == "__main__": main()