Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ 6e2dc934

History | View | Annotate | Download (16.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
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.
10
#
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.
15
#
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
19
# 02110-1301, USA.
20

    
21

    
22
"""Burnin program
23

    
24
"""
25

    
26
import os
27
import sys
28
import optparse
29
import time
30
from itertools import izip, islice, cycle
31
from cStringIO import StringIO
32

    
33
from ganeti import opcodes
34
from ganeti import mcpu
35
from ganeti import constants
36
from ganeti import cli
37
from ganeti import logger
38
from ganeti import errors
39
from ganeti import utils
40

    
41

    
42
USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
43

    
44

    
45
def Usage():
46
  """Shows program usage information and exits the program."""
47

    
48
  print >> sys.stderr, "Usage:"
49
  print >> sys.stderr, USAGE
50
  sys.exit(2)
51

    
52

    
53
def Log(msg):
54
  """Simple function that prints out its argument.
55

    
56
  """
57
  print msg
58
  sys.stdout.flush()
59

    
60

    
61
class Burner(object):
62
  """Burner class."""
63

    
64
  def __init__(self):
65
    """Constructor."""
66
    logger.SetupLogging(constants.LOG_BURNIN, debug=False, stderr_logging=True)
67
    self._feed_buf = StringIO()
68
    self.nodes = []
69
    self.instances = []
70
    self.to_rem = []
71
    self.opts = None
72
    self.ParseOptions()
73
    self.GetState()
74

    
75
  def ClearFeedbackBuf(self):
76
    """Clear the feedback buffer."""
77
    self._feed_buf.truncate(0)
78

    
79
  def GetFeedbackBuf(self):
80
    """Return the contents of the buffer."""
81
    return self._feed_buf.getvalue()
82

    
83
  def Feedback(self, msg):
84
    """Acumulate feedback in our buffer."""
85
    self._feed_buf.write("%s %s\n" % (time.ctime(utils.MergeTime(msg[0])),
86
                                      msg[2]))
87
    if self.opts.verbose:
88
      Log(msg)
89

    
90
  def ExecOp(self, op):
91
    """Execute an opcode and manage the exec buffer."""
92
    self.ClearFeedbackBuf()
93
    return cli.SubmitOpCode(op, feedback_fn=self.Feedback)
94

    
95
  def ParseOptions(self):
96
    """Parses the command line options.
97

    
98
    In case of command line errors, it will show the usage and exit the
99
    program.
100

    
101
    """
102

    
103
    parser = optparse.OptionParser(usage="\n%s" % USAGE,
104
                                   version="%%prog (ganeti) %s" %
105
                                   constants.RELEASE_VERSION,
106
                                   option_class=cli.CliOption)
107

    
108
    parser.add_option("-o", "--os", dest="os", default=None,
109
                      help="OS to use during burnin",
110
                      metavar="<OS>")
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",
128
                      default=True)
129
    parser.add_option("--no-importexport", dest="do_importexport",
130
                      help="Skip instance export/import", action="store_false",
131
                      default=True)
132
    parser.add_option("--no-startstop", dest="do_startstop",
133
                      help="Skip instance stop/start", action="store_false",
134
                      default=True)
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"),
141
                      default="drbd",
142
                      help="Disk template (diskless, file, plain or drbd)"
143
                            " [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")
152

    
153
    options, args = parser.parse_args()
154
    if len(args) < 1 or options.os is None:
155
      Usage()
156

    
157
    supported_disk_templates = (constants.DT_DISKLESS,
158
                                constants.DT_FILE,
159
                                constants.DT_PLAIN,
160
                                constants.DT_DRBD8)
161
    if options.disk_template not in supported_disk_templates:
162
      Log("Unknown disk template '%s'" % options.disk_template)
163
      sys.exit(1)
164

    
165
    if options.nodes and options.iallocator:
166
      Log("Give either the nodes option or the iallocator option, not both")
167
      sys.exit(1)
168

    
169
    self.opts = options
170
    self.instances = args
171

    
172
  def GetState(self):
173
    """Read the cluster state from the config."""
174
    if self.opts.nodes:
175
      names = self.opts.nodes.split(",")
176
    else:
177
      names = []
178
    try:
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)
183
      Log(msg)
184
      sys.exit(err_code)
185
    self.nodes = [data[0] for data in result]
186

    
187
    result = self.ExecOp(opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
188
                                              names=[]))
189

    
190
    if not result:
191
      Log("Can't get the OS list")
192
      sys.exit(1)
193

    
194
    # filter non-valid OS-es
195
    os_set = [val[0] for val in result if val[1]]
196

    
197
    if self.opts.os not in os_set:
198
      Log("OS '%s' not found" % self.opts.os)
199
      sys.exit(1)
200

    
201
  def CreateInstances(self):
202
    """Create the given instances.
203

    
204
    """
205
    self.to_rem = []
206
    mytor = izip(cycle(self.nodes),
207
                 islice(cycle(self.nodes), 1, None),
208
                 self.instances)
209
    for pnode, snode, instance in mytor:
210
      if self.opts.iallocator:
211
        pnode = snode = None
212
        Log("- Add instance %s (iallocator: %s)" %
213
              (instance, self.opts.iallocator))
214
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
215
        snode = None
216
        Log("- Add instance %s on node %s" % (instance, pnode))
217
      else:
218
        Log("- Add instance %s on nodes %s/%s" % (instance, pnode, snode))
219

    
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,
227
                                    pnode=pnode,
228
                                    snode=snode,
229
                                    vcpus=1,
230
                                    start=True,
231
                                    ip_check=True,
232
                                    wait_for_sync=True,
233
                                    mac="auto",
234
                                    kernel_path=None,
235
                                    initrd_path=None,
236
                                    hvm_boot_order=None,
237
                                    file_driver="loop",
238
                                    file_storage_dir=None,
239
                                    iallocator=self.opts.iallocator,
240
                                    auto_balance=True,
241
                                    hvm_nic_type=constants.HT_HVM_NIC_RTL8139,
242
                                    hvm_disk_type=constants.HT_HVM_DEV_IOEMU)
243

    
244
      self.ExecOp(op)
245
      self.to_rem.append(instance)
246

    
247
  def ReplaceDisks1D8(self):
248
    """Replace disks on primary and secondary for drbd8."""
249
    for instance in self.instances:
250
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
251
        op = opcodes.OpReplaceDisks(instance_name=instance,
252
                                    mode=mode,
253
                                    disks=["sda", "sdb"])
254
        Log("- Replace disks (%s) for instance %s" % (mode, instance))
255
        self.ExecOp(op)
256

    
257
  def ReplaceDisks2(self):
258
    """Replace secondary node."""
259
    mode = constants.REPLACE_DISK_SEC
260

    
261
    mytor = izip(islice(cycle(self.nodes), 2, None),
262
                 self.instances)
263
    for tnode, instance in mytor:
264
      if self.opts.iallocator:
265
        tnode = None
266
      op = opcodes.OpReplaceDisks(instance_name=instance,
267
                                  mode=mode,
268
                                  remote_node=tnode,
269
                                  iallocator=self.opts.iallocator,
270
                                  disks=["sda", "sdb"])
271
      Log("- Replace secondary (%s) for instance %s" % (mode, instance))
272
      self.ExecOp(op)
273

    
274
  def Failover(self):
275
    """Failover the instances."""
276

    
277
    for instance in self.instances:
278
      op = opcodes.OpFailoverInstance(instance_name=instance,
279
                                      ignore_consistency=False)
280

    
281
      Log("- Failover instance %s" % (instance))
282
      self.ExecOp(op)
283

    
284
  def ImportExport(self):
285
    """Export the instance, delete it, and import it back.
286

    
287
    """
288

    
289
    mytor = izip(cycle(self.nodes),
290
                 islice(cycle(self.nodes), 1, None),
291
                 islice(cycle(self.nodes), 2, None),
292
                 self.instances)
293

    
294
    for pnode, snode, enode, instance in mytor:
295

    
296
      if self.opts.iallocator:
297
        pnode = snode = None
298
        import_log_msg = ("- Import instance %s from node %s (iallocator: %s)" %
299
                          (instance, enode, self.opts.iallocator))
300
      elif self.opts.disk_template not in constants.DTS_NET_MIRROR:
301
        snode = None
302
        import_log_msg = ("- Import instance %s from node %s to node %s" %
303
                          (instance, enode, pnode))
304
      else:
305
        import_log_msg = ("- Import instance %s from node %s to nodes %s/%s" %
306
                          (instance, enode, pnode, snode))
307

    
308
      exp_op = opcodes.OpExportInstance(instance_name=instance,
309
                                           target_node=enode,
310
                                           shutdown=True)
311
      rem_op = opcodes.OpRemoveInstance(instance_name=instance,
312
                                        ignore_failures=True)
313
      nam_op = opcodes.OpQueryInstances(output_fields=["name"],
314
                                           names=[instance])
315
      full_name = self.ExecOp(nam_op)[0][0]
316
      imp_dir = os.path.join(constants.EXPORT_DIR, full_name)
317
      imp_op = opcodes.OpCreateInstance(instance_name=instance,
318
                                        mem_size=128,
319
                                        disk_size=self.opts.os_size,
320
                                        swap_size=self.opts.swap_size,
321
                                        disk_template=self.opts.disk_template,
322
                                        mode=constants.INSTANCE_IMPORT,
323
                                        src_node=enode,
324
                                        src_path=imp_dir,
325
                                        pnode=pnode,
326
                                        snode=snode,
327
                                        vcpus=1,
328
                                        start=True,
329
                                        ip_check=True,
330
                                        wait_for_sync=True,
331
                                        mac="auto",
332
                                        file_storage_dir=None,
333
                                        file_driver=None,
334
                                        iallocator=self.opts.iallocator,
335
                                        auto_balance=True,
336
                                        hvm_nic_type=
337
                                        constants.HT_HVM_NIC_RTL8139,
338
                                        hvm_disk_type=
339
                                        constants.HT_HVM_DEV_IOEMU)
340

    
341
      erem_op = opcodes.OpRemoveExport(instance_name=instance)
342

    
343
      Log("- Export instance %s to node %s" % (instance, enode))
344
      self.ExecOp(exp_op)
345
      Log("- Remove instance %s" % (instance))
346
      self.ExecOp(rem_op)
347
      self.to_rem.remove(instance)
348
      Log(import_log_msg)
349
      self.ExecOp(imp_op)
350
      Log("- Remove export of instance %s" % (instance))
351
      self.ExecOp(erem_op)
352

    
353
      self.to_rem.append(instance)
354

    
355
  def StopInstance(self, instance):
356
    """Stop given instance."""
357
    op = opcodes.OpShutdownInstance(instance_name=instance)
358
    Log("- Shutdown instance %s" % instance)
359
    self.ExecOp(op)
360

    
361
  def StartInstance(self, instance):
362
    """Start given instance."""
363
    op = opcodes.OpStartupInstance(instance_name=instance, force=False)
364
    Log("- Start instance %s" % instance)
365
    self.ExecOp(op)
366

    
367
  def RenameInstance(self, instance, instance_new):
368
    """Rename instance."""
369
    op = opcodes.OpRenameInstance(instance_name=instance,
370
                                  new_name=instance_new)
371
    Log("- Rename instance %s to %s" % (instance, instance_new))
372
    self.ExecOp(op)
373

    
374
  def StopStart(self):
375
    """Stop/start the instances."""
376
    for instance in self.instances:
377
      self.StopInstance(instance)
378
      self.StartInstance(instance)
379

    
380
  def Remove(self):
381
    """Remove the instances."""
382
    for instance in self.to_rem:
383
      op = opcodes.OpRemoveInstance(instance_name=instance,
384
                                    ignore_failures=True)
385
      Log("- Remove instance %s" % instance)
386
      self.ExecOp(op)
387

    
388

    
389
  def Rename(self):
390
    """Rename the instances."""
391
    rename = self.opts.rename
392
    for instance in self.instances:
393
      self.StopInstance(instance)
394
      self.RenameInstance(instance, rename)
395
      self.StartInstance(rename)
396
      self.StopInstance(rename)
397
      self.RenameInstance(rename, instance)
398
      self.StartInstance(instance)
399

    
400
  def BurninCluster(self):
401
    """Test a cluster intensively.
402

    
403
    This will create instances and then start/stop/failover them.
404
    It is safe for existing instances but could impact performance.
405

    
406
    """
407

    
408
    opts = self.opts
409

    
410
    Log("- Testing global parameters")
411

    
412
    if (len(self.nodes) == 1 and
413
        opts.disk_template not in (constants.DT_DISKLESS, constants.DT_PLAIN,
414
                                   constants.DT_FILE)):
415
      Log("When one node is available/selected the disk template must"
416
          " be 'diskless', 'file' or 'plain'")
417
      sys.exit(1)
418

    
419
    has_err = True
420
    try:
421
      self.CreateInstances()
422
      if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
423
        self.ReplaceDisks1D8()
424
      if (opts.do_replace2 and len(self.nodes) > 2 and
425
          opts.disk_template in constants.DTS_NET_MIRROR) :
426
        self.ReplaceDisks2()
427

    
428
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
429
        self.Failover()
430

    
431
      if opts.do_importexport:
432
        self.ImportExport()
433

    
434
      if opts.do_startstop:
435
        self.StopStart()
436

    
437
      if opts.rename:
438
        self.Rename()
439

    
440
      has_err = False
441
    finally:
442
      if has_err:
443
        Log("Error detected: opcode buffer follows:\n\n")
444
        Log(self.GetFeedbackBuf())
445
        Log("\n\n")
446
      self.Remove()
447

    
448
    return 0
449

    
450

    
451
def main():
452
  """Main function"""
453

    
454
  burner = Burner()
455
  return burner.BurninCluster()
456

    
457

    
458
if __name__ == "__main__":
459
  main()