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
28 from itertools import izip, islice, cycle
29 from cStringIO import StringIO
31 from ganeti import opcodes
32 from ganeti import mcpu
33 from ganeti import constants
34 from ganeti import cli
35 from ganeti import logger
36 from ganeti import errors
37 from ganeti import utils
40 USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
44 """Shows program usage information and exits the program."""
46 print >> sys.stderr, "Usage:"
47 print >> sys.stderr, USAGE
52 """Simple function that prints out its argument.
64 logger.SetupLogging(debug=False, program="ganeti/burnin")
65 self._feed_buf = StringIO()
66 self.proc = mcpu.Processor(feedback=self.Feedback)
74 def ClearFeedbackBuf(self):
75 """Clear the feedback buffer."""
76 self._feed_buf.truncate(0)
78 def GetFeedbackBuf(self):
79 """Return the contents of the buffer."""
80 return self._feed_buf.getvalue()
82 def Feedback(self, msg):
83 """Acumulate feedback in our buffer."""
84 self._feed_buf.write(msg)
85 self._feed_buf.write("\n")
88 """Execute an opcode and manage the exec buffer."""
89 self.ClearFeedbackBuf()
90 return self.proc.ExecOpCode(op)
92 def ParseOptions(self):
93 """Parses the command line options.
95 In case of command line errors, it will show the usage and exit the
100 parser = optparse.OptionParser(usage="\n%s" % USAGE,
101 version="%%prog (ganeti) %s" %
102 constants.RELEASE_VERSION,
103 option_class=cli.CliOption)
105 parser.add_option("-o", "--os", dest="os", default=None,
106 help="OS to use during burnin",
108 parser.add_option("--os-size", dest="os_size", help="Disk size",
109 default=4 * 1024, type="unit", metavar="<size>")
110 parser.add_option("--swap-size", dest="swap_size", help="Swap size",
111 default=4 * 1024, type="unit", metavar="<size>")
112 parser.add_option("-v", "--verbose",
113 action="store_true", dest="verbose", default=False,
114 help="print command execution messages to stdout")
115 parser.add_option("--no-replace1", dest="do_replace1",
116 help="Skip disk replacement with the same secondary",
117 action="store_false", default=True)
118 parser.add_option("--no-replace2", dest="do_replace2",
119 help="Skip disk replacement with a different secondary",
120 action="store_false", default=True)
121 parser.add_option("--no-failover", dest="do_failover",
122 help="Skip instance failovers", action="store_false",
124 parser.add_option("-t", "--disk-template", dest="disk_template",
125 choices=("remote_raid1", "drbd"),
126 default="remote_raid1",
127 help="Template type for network mirroring (remote_raid1"
128 " or drbd) [remote_raid1]")
129 parser.add_option("-n", "--nodes", dest="nodes", default="",
130 help="Comma separated list of nodes to perform"
131 " the burnin on (defaults to all nodes)")
133 options, args = parser.parse_args()
134 if len(args) < 1 or options.os is None:
137 supported_disk_templates = (constants.DT_PLAIN, constants.DT_REMOTE_RAID1,
139 if options.disk_template not in supported_disk_templates:
140 Log("Unknown disk template '%s'" % options.disk_template)
144 self.instances = args
147 """Read the cluster state from the config."""
149 names = self.opts.nodes.split(",")
153 op = opcodes.OpQueryNodes(output_fields=["name"], names=names)
154 result = self.ExecOp(op)
155 except errors.GenericError, err:
156 err_code, msg = cli.FormatError(err)
159 self.nodes = [data[0] for data in result]
161 result = self.ExecOp(opcodes.OpDiagnoseOS())
164 Log("Can't get the OS list")
167 # filter non-valid OS-es
169 for node_name in result:
170 oses[node_name] = [obj for obj in result[node_name] if obj]
172 fnode = oses.keys()[0]
173 os_set = set([os_inst.name for os_inst in oses[fnode]])
176 os_set &= set([os_inst.name for os_inst in oses[node]])
178 if self.opts.os not in os_set:
179 Log("OS '%s' not found" % self.opts.os)
182 def CreateInstances(self):
183 """Create the given instances.
187 mytor = izip(cycle(self.nodes),
188 islice(cycle(self.nodes), 1, None),
190 for pnode, snode, instance in mytor:
191 op = opcodes.OpCreateInstance(instance_name=instance,
193 disk_size=self.opts.os_size,
194 swap_size=self.opts.swap_size,
195 disk_template=self.opts.disk_template,
196 mode=constants.INSTANCE_CREATE,
197 os_type=self.opts.os,
208 Log("- Add instance %s on node %s" % (instance, pnode))
210 self.to_rem.append(instance)
212 def ReplaceDisks1R1(self):
213 """Replace disks with the same secondary for rr1."""
214 # replace all, both disks
215 for instance in self.instances:
216 op = opcodes.OpReplaceDisks(instance_name=instance,
218 mode=constants.REPLACE_DISK_ALL,
219 disks=["sda", "sdb"])
221 Log("- Replace disks for instance %s" % (instance))
224 def ReplaceDisks1D8(self):
225 """Replace disks on primary and secondary for drbd8."""
226 for instance in self.instances:
227 for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
228 op = opcodes.OpReplaceDisks(instance_name=instance,
230 disks=["sda", "sdb"])
231 Log("- Replace disks (%s) for instance %s" % (mode, instance))
234 def ReplaceDisks2(self):
235 """Replace secondary node."""
236 if self.opts.disk_template == constants.DT_REMOTE_RAID1:
237 mode = constants.REPLACE_DISK_ALL
239 mode = constants.REPLACE_DISK_SEC
241 mytor = izip(islice(cycle(self.nodes), 2, None),
243 for tnode, instance in mytor:
244 op = opcodes.OpReplaceDisks(instance_name=instance,
247 disks=["sda", "sdb"])
248 Log("- Replace secondary (%s) for instance %s" % (mode, instance))
252 """Failover the instances."""
254 for instance in self.instances:
255 op = opcodes.OpFailoverInstance(instance_name=instance,
256 ignore_consistency=False)
258 Log("- Failover instance %s" % (instance))
262 """Stop/start the instances."""
263 for instance in self.instances:
264 op = opcodes.OpShutdownInstance(instance_name=instance)
265 Log("- Shutdown instance %s" % instance)
267 op = opcodes.OpStartupInstance(instance_name=instance, force=False)
268 Log("- Start instance %s" % instance)
272 """Remove the instances."""
273 for instance in self.to_rem:
274 op = opcodes.OpRemoveInstance(instance_name=instance)
275 Log("- Remove instance %s" % instance)
278 def BurninCluster(self):
279 """Test a cluster intensively.
281 This will create instances and then start/stop/failover them.
282 It is safe for existing instances but could impact performance.
288 Log("- Testing global parameters")
290 if len(self.nodes) == 1 and opts.disk_template != constants.DT_PLAIN:
291 Log("When one node is available/selected the disk template must"
297 self.CreateInstances()
298 if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
299 if opts.disk_template == constants.DT_REMOTE_RAID1:
300 self.ReplaceDisks1R1()
301 elif opts.disk_template == constants.DT_DRBD8:
302 self.ReplaceDisks1D8()
303 if (opts.do_replace2 and len(self.nodes) > 2 and
304 opts.disk_template in constants.DTS_NET_MIRROR) :
307 if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
314 Log("Error detected: opcode buffer follows:\n\n")
315 Log(self.GetFeedbackBuf())
327 utils.Lock('cmd', max_retries=15, debug=True)
328 except errors.LockError, err:
329 logger.ToStderr(str(err))
332 retval = burner.BurninCluster()
339 if __name__ == "__main__":