Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ c2c2a903

History | View | Annotate | Download (16.2 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
                                    hvm_nic_type=constants.HT_HVM_NIC_RTL8139,
241
                                    hvm_disk_type=constants.HT_HVM_DEV_IOEMU)
242

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

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

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

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

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

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

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

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

    
286
    """
287

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

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

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

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

    
339
      erem_op = opcodes.OpRemoveExport(instance_name=instance)
340

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

    
351
      self.to_rem.append(instance)
352

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

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

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

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

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

    
386

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

    
398
  def BurninCluster(self):
399
    """Test a cluster intensively.
400

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

    
404
    """
405

    
406
    opts = self.opts
407

    
408
    Log("- Testing global parameters")
409

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

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

    
426
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
427
        self.Failover()
428

    
429
      if opts.do_importexport:
430
        self.ImportExport()
431

    
432
      if opts.do_startstop:
433
        self.StopStart()
434

    
435
      if opts.rename:
436
        self.Rename()
437

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

    
446
    return 0
447

    
448

    
449
def main():
450
  """Main function"""
451

    
452
  burner = Burner()
453
  return burner.BurninCluster()
454

    
455

    
456
if __name__ == "__main__":
457
  main()