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
29 from itertools import izip, islice, cycle
30 from cStringIO import StringIO
32 from ganeti import opcodes
33 from ganeti import mcpu
34 from ganeti import constants
35 from ganeti import cli
36 from ganeti import logger
37 from ganeti import errors
38 from ganeti import utils
41 USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
45 """Shows program usage information and exits the program."""
47 print >> sys.stderr, "Usage:"
48 print >> sys.stderr, USAGE
53 """Simple function that prints out its argument.
65 logger.SetupLogging(debug=False, program="ganeti/burnin")
66 self._feed_buf = StringIO()
67 self.proc = mcpu.Processor(feedback=self.Feedback)
75 def ClearFeedbackBuf(self):
76 """Clear the feedback buffer."""
77 self._feed_buf.truncate(0)
79 def GetFeedbackBuf(self):
80 """Return the contents of the buffer."""
81 return self._feed_buf.getvalue()
83 def Feedback(self, msg):
84 """Acumulate feedback in our buffer."""
85 self._feed_buf.write(msg)
86 self._feed_buf.write("\n")
91 """Execute an opcode and manage the exec buffer."""
92 self.ClearFeedbackBuf()
93 return cli.SubmitOpCode(op)
95 def ParseOptions(self):
96 """Parses the command line options.
98 In case of command line errors, it will show the usage and exit the
103 parser = optparse.OptionParser(usage="\n%s" % USAGE,
104 version="%%prog (ganeti) %s" %
105 constants.RELEASE_VERSION,
106 option_class=cli.CliOption)
108 parser.add_option("-o", "--os", dest="os", default=None,
109 help="OS to use during burnin",
111 parser.add_option("--os-size", dest="os_size", help="Disk size",
112 default=4 * 1024, type="unit", metavar="<size>")
113 parser.add_option("--swap-size", dest="swap_size", help="Swap size",
114 default=4 * 1024, type="unit", metavar="<size>")
115 parser.add_option("--mem-size", dest="mem_size", help="Memory size",
116 default=128, type="unit", metavar="<size>")
117 parser.add_option("-v", "--verbose",
118 action="store_true", dest="verbose", default=False,
119 help="print command execution messages to stdout")
120 parser.add_option("--no-replace1", dest="do_replace1",
121 help="Skip disk replacement with the same secondary",
122 action="store_false", default=True)
123 parser.add_option("--no-replace2", dest="do_replace2",
124 help="Skip disk replacement with a different secondary",
125 action="store_false", default=True)
126 parser.add_option("--no-failover", dest="do_failover",
127 help="Skip instance failovers", action="store_false",
129 parser.add_option("--no-importexport", dest="do_importexport",
130 help="Skip instance export/import", action="store_false",
132 parser.add_option("--no-startstop", dest="do_startstop",
133 help="Skip instance stop/start", action="store_false",
135 parser.add_option("--rename", dest="rename", default=None,
136 help="Give one unused instance name which is taken"
137 " to start the renaming sequence",
138 metavar="<instance_name>")
139 parser.add_option("-t", "--disk-template", dest="disk_template",
140 choices=("diskless", "file", "plain", "drbd"),
142 help="Disk template (diskless, file, plain or drbd)"
144 parser.add_option("-n", "--nodes", dest="nodes", default="",
145 help="Comma separated list of nodes to perform"
146 " the burnin on (defaults to all nodes)")
147 parser.add_option("--iallocator", dest="iallocator",
148 default=None, type="string",
149 help="Perform the allocation using an iallocator"
150 " instead of fixed node spread (node restrictions no"
151 " longer apply, therefore -n/--nodes must not be used")
153 options, args = parser.parse_args()
154 if len(args) < 1 or options.os is None:
157 supported_disk_templates = (constants.DT_DISKLESS,
161 if options.disk_template not in supported_disk_templates:
162 Log("Unknown disk template '%s'" % options.disk_template)
165 if options.nodes and options.iallocator:
166 Log("Give either the nodes option or the iallocator option, not both")
170 self.instances = args
173 """Read the cluster state from the config."""
175 names = self.opts.nodes.split(",")
179 op = opcodes.OpQueryNodes(output_fields=["name"], names=names)
180 result = self.ExecOp(op)
181 except errors.GenericError, err:
182 err_code, msg = cli.FormatError(err)
185 self.nodes = [data[0] for data in result]
187 result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
191 Log("Can't get the OS list")
194 # filter non-valid OS-es
195 os_set = [val[0] for val in result if val[1]]
197 if self.opts.os not in os_set:
198 Log("OS '%s' not found" % self.opts.os)
201 def CreateInstances(self):
202 """Create the given instances.
206 mytor = izip(cycle(self.nodes),
207 islice(cycle(self.nodes), 1, None),
209 for pnode, snode, instance in mytor:
210 if self.opts.iallocator:
212 Log("- Add instance %s (iallocator: %s)" %
213 (instance, self.opts.iallocator))
214 elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
216 Log("- Add instance %s on node %s" % (instance, pnode))
218 Log("- Add instance %s on nodes %s/%s" % (instance, pnode, snode))
220 op = opcodes.OpCreateInstance(instance_name=instance,
221 mem_size=self.opts.mem_size,
222 disk_size=self.opts.os_size,
223 swap_size=self.opts.swap_size,
224 disk_template=self.opts.disk_template,
225 mode=constants.INSTANCE_CREATE,
226 os_type=self.opts.os,
238 file_storage_dir=None,
239 iallocator=self.opts.iallocator)
241 self.to_rem.append(instance)
243 def ReplaceDisks1D8(self):
244 """Replace disks on primary and secondary for drbd8."""
245 for instance in self.instances:
246 for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
247 op = opcodes.OpReplaceDisks(instance_name=instance,
249 disks=["sda", "sdb"])
250 Log("- Replace disks (%s) for instance %s" % (mode, instance))
253 def ReplaceDisks2(self):
254 """Replace secondary node."""
255 mode = constants.REPLACE_DISK_SEC
257 mytor = izip(islice(cycle(self.nodes), 2, None),
259 for tnode, instance in mytor:
260 if self.opts.iallocator:
262 op = opcodes.OpReplaceDisks(instance_name=instance,
265 iallocator=self.opts.iallocator,
266 disks=["sda", "sdb"])
267 Log("- Replace secondary (%s) for instance %s" % (mode, instance))
271 """Failover the instances."""
273 for instance in self.instances:
274 op = opcodes.OpFailoverInstance(instance_name=instance,
275 ignore_consistency=False)
277 Log("- Failover instance %s" % (instance))
280 def ImportExport(self):
281 """Export the instance, delete it, and import it back.
285 mytor = izip(cycle(self.nodes),
286 islice(cycle(self.nodes), 1, None),
287 islice(cycle(self.nodes), 2, None),
290 for pnode, snode, enode, instance in mytor:
291 exp_op = opcodes.OpExportInstance(instance_name=instance,
294 rem_op = opcodes.OpRemoveInstance(instance_name=instance)
295 nam_op = opcodes.OpQueryInstances(output_fields=["name"],
297 full_name = self.ExecOp(nam_op)[0][0]
298 imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
299 imp_op = opcodes.OpCreateInstance(instance_name=instance,
301 disk_size=self.opts.os_size,
302 swap_size=self.opts.swap_size,
303 disk_template=self.opts.disk_template,
304 mode=constants.INSTANCE_IMPORT,
314 file_storage_dir=None,
316 erem_op = opcodes.OpRemoveExport(instance_name=instance)
318 Log("- Export instance %s to node %s" % (instance, enode))
320 Log("- Remove instance %s" % (instance))
322 self.to_rem.remove(instance)
323 Log("- Import instance %s from node %s to node %s" %
324 (instance, enode, pnode))
326 Log("- Remove export of instance %s" % (instance))
329 self.to_rem.append(instance)
331 def StopInstance(self, instance):
332 """Stop given instance."""
333 op = opcodes.OpShutdownInstance(instance_name=instance)
334 Log("- Shutdown instance %s" % instance)
337 def StartInstance(self, instance):
338 """Start given instance."""
339 op = opcodes.OpStartupInstance(instance_name=instance, force=False)
340 Log("- Start instance %s" % instance)
343 def RenameInstance(self, instance, instance_new):
344 """Rename instance."""
345 op = opcodes.OpRenameInstance(instance_name=instance,
346 new_name=instance_new)
347 Log("- Rename instance %s to %s" % (instance, instance_new))
351 """Stop/start the instances."""
352 for instance in self.instances:
353 self.StopInstance(instance)
354 self.StartInstance(instance)
357 """Remove the instances."""
358 for instance in self.to_rem:
359 op = opcodes.OpRemoveInstance(instance_name=instance)
360 Log("- Remove instance %s" % instance)
365 """Rename the instances."""
366 rename = self.opts.rename
367 for instance in self.instances:
368 self.StopInstance(instance)
369 self.RenameInstance(instance, rename)
370 self.StartInstance(rename)
371 self.StopInstance(rename)
372 self.RenameInstance(rename, instance)
373 self.StartInstance(instance)
375 def BurninCluster(self):
376 """Test a cluster intensively.
378 This will create instances and then start/stop/failover them.
379 It is safe for existing instances but could impact performance.
385 Log("- Testing global parameters")
387 if (len(self.nodes) == 1 and
388 opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
390 Log("When one node is available/selected the disk template must"
391 " be 'diskless', 'file' or 'plain'")
396 self.CreateInstances()
397 if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
398 self.ReplaceDisks1D8()
399 if (opts.do_replace2 and len(self.nodes) > 2 and
400 opts.disk_template in constants.DTS_NET_MIRROR) :
403 if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
406 if opts.do_importexport:
409 if opts.do_startstop:
418 Log("Error detected: opcode buffer follows:\n\n")
419 Log(self.GetFeedbackBuf())
431 utils.Lock('cmd', max_retries=15, debug=True)
432 except errors.LockError, err:
433 logger.ToStderr(str(err))
436 retval = burner.BurninCluster()
443 if __name__ == "__main__":