Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ 21546b1c

History | View | Annotate | Download (10 kB)

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

    
4
"""Burnin program"""
5

    
6
import sys
7
import optparse
8
from itertools import izip, islice, cycle
9
from cStringIO import StringIO
10

    
11
from ganeti import opcodes
12
from ganeti import mcpu
13
from ganeti import constants
14
from ganeti import cli
15
from ganeti import logger
16
from ganeti import errors
17
from ganeti import utils
18

    
19
USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
20

    
21
def Usage():
22
  """Shows program usage information and exits the program."""
23

    
24
  print >> sys.stderr, "Usage:"
25
  print >> sys.stderr, USAGE
26
  sys.exit(2)
27

    
28
def Log(msg):
29
  """Simple function that prints out its argument.
30

    
31
  """
32
  print msg
33

    
34
class Burner(object):
35
  """Burner class."""
36

    
37
  def __init__(self):
38
    """Constructor."""
39
    logger.SetupLogging(debug=False, program="ganeti/burnin")
40
    self._feed_buf = StringIO()
41
    self.proc = mcpu.Processor(feedback=self.Feedback)
42
    self.nodes = []
43
    self.instances = []
44
    self.to_rem = []
45
    self.opts = None
46
    self.ParseOptions()
47
    self.GetState()
48

    
49
  def ClearFeedbackBuf(self):
50
    """Clear the feedback buffer."""
51
    self._feed_buf.truncate(0)
52

    
53
  def GetFeedbackBuf(self):
54
    """Return the contents of the buffer."""
55
    return self._feed_buf.getvalue()
56

    
57
  def Feedback(self, msg):
58
    """Acumulate feedback in our buffer."""
59
    self._feed_buf.write(msg)
60
    self._feed_buf.write("\n")
61

    
62
  def ExecOp(self, op):
63
    """Execute an opcode and manage the exec buffer."""
64
    self.ClearFeedbackBuf()
65
    return self.proc.ExecOpCode(op)
66

    
67
  def ParseOptions(self):
68
    """Parses the command line options.
69

    
70
    In case of command line errors, it will show the usage and exit the
71
    program.
72

    
73
    """
74

    
75
    parser = optparse.OptionParser(usage="\n%s" % USAGE,
76
                                   version="%%prog (ganeti) %s" %
77
                                   constants.RELEASE_VERSION,
78
                                   option_class=cli.CliOption)
79

    
80
    parser.add_option("-o", "--os", dest="os", default=None,
81
                      help="OS to use during burnin",
82
                      metavar="<OS>")
83
    parser.add_option("--os-size", dest="os_size", help="Disk size",
84
                      default=4 * 1024, type="unit", metavar="<size>")
85
    parser.add_option("--swap-size", dest="swap_size", help="Swap size",
86
                      default=4 * 1024, type="unit", metavar="<size>")
87
    parser.add_option("-v", "--verbose",
88
                      action="store_true", dest="verbose", default=False,
89
                      help="print command execution messages to stdout")
90
    parser.add_option("--no-replace1", dest="do_replace1",
91
                      help="Skip disk replacement with the same secondary",
92
                      action="store_false", default=True)
93
    parser.add_option("--no-replace2", dest="do_replace2",
94
                      help="Skip disk replacement with a different secondary",
95
                      action="store_false", default=True)
96
    parser.add_option("--no-failover", dest="do_failover",
97
                      help="Skip instance failovers", action="store_false",
98
                      default=True)
99
    parser.add_option("-t", "--disk-template", dest="disk_template",
100
                      choices=("remote_raid1", "drbd8"),
101
                      default="remote_raid1",
102
                      help="Template type for network mirroring (remote_raid1"
103
                      " or drbd8) [remote_raid1]")
104
    parser.add_option("-n", "--nodes", dest="nodes", default="",
105
                      help="Comma separated list of nodes to perform"
106
                      " the burnin on (defaults to all nodes)")
107

    
108
    options, args = parser.parse_args()
109
    if len(args) < 1 or options.os is None:
110
      Usage()
111

    
112
    if options.disk_template == "plain":
113
      disk_template = constants.DT_PLAIN
114
    elif options.disk_template == "remote_raid1":
115
      disk_template = constants.DT_REMOTE_RAID1
116
    elif options.disk_template == "drbd8":
117
      disk_template = constants.DT_DRBD8
118
    else:
119
      Log("Unknown disk template '%s'" % options.disk_template)
120
      sys.exit(1)
121

    
122
    options.disk_template = disk_template
123
    self.opts = options
124
    self.instances = args
125

    
126
  def GetState(self):
127
    """Read the cluster state from the config."""
128
    if self.opts.nodes:
129
      names = self.opts.nodes.split(",")
130
    else:
131
      names = []
132
    try:
133
      op = opcodes.OpQueryNodes(output_fields=["name"], names=names)
134
      result = self.ExecOp(op)
135
    except errors.GenericError, err:
136
      err_code, msg = cli.FormatError(err)
137
      Log(msg)
138
      sys.exit(err_code)
139
    self.nodes = [data[0] for data in result]
140

    
141
    result = self.ExecOp(opcodes.OpDiagnoseOS())
142

    
143
    if not result:
144
      Log("Can't get the OS list")
145
      sys.exit(1)
146

    
147
    # filter non-valid OS-es
148
    oses = {}
149
    for node_name in result:
150
      oses[node_name] = [obj for obj in result[node_name] if obj]
151

    
152
    fnode = oses.keys()[0]
153
    os_set = set([os_inst.name for os_inst in oses[fnode]])
154
    del oses[fnode]
155
    for node in oses:
156
      os_set &= set([os_inst.name for os_inst in oses[node]])
157

    
158
    if self.opts.os not in os_set:
159
      Log("OS '%s' not found" % self.opts.os)
160
      sys.exit(1)
161

    
162
  def CreateInstances(self):
163
    """Create the given instances.
164

    
165
    """
166
    self.to_rem = []
167
    mytor = izip(cycle(self.nodes),
168
                 islice(cycle(self.nodes), 1, None),
169
                 self.instances)
170
    for pnode, snode, instance in mytor:
171
      op = opcodes.OpCreateInstance(instance_name=instance,
172
                                    mem_size=128,
173
                                    disk_size=self.opts.os_size,
174
                                    swap_size=self.opts.swap_size,
175
                                    disk_template=self.opts.disk_template,
176
                                    mode=constants.INSTANCE_CREATE,
177
                                    os_type=self.opts.os,
178
                                    pnode=pnode,
179
                                    snode=snode,
180
                                    vcpus=1,
181
                                    start=True,
182
                                    ip_check=True,
183
                                    wait_for_sync=True)
184
      Log("- Add instance %s on node %s" % (instance, pnode))
185
      self.ExecOp(op)
186
      self.to_rem.append(instance)
187

    
188
  def ReplaceDisks1R1(self):
189
    """Replace disks with the same secondary for rr1."""
190
    # replace all, both disks
191
    for instance in self.instances:
192
      op = opcodes.OpReplaceDisks(instance_name=instance,
193
                                  remote_node=None,
194
                                  mode=constants.REPLACE_DISK_ALL,
195
                                  disks=["sda", "sdb"])
196

    
197
      Log("- Replace disks for instance %s" % (instance))
198
      self.ExecOp(op)
199

    
200
  def ReplaceDisks1D8(self):
201
    """Replace disks on primary and secondary for drbd8."""
202
    for instance in self.instances:
203
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
204
        op = opcodes.OpReplaceDisks(instance_name=instance,
205
                                    mode=mode,
206
                                    disks=["sda", "sdb"])
207
        Log("- Replace disks (%s) for instance %s" % (mode, instance))
208
        self.ExecOp(op)
209

    
210
  def ReplaceDisks2(self):
211
    """Replace secondary node."""
212
    if self.opts.disk_template == constants.DT_REMOTE_RAID1:
213
      mode = constants.REPLACE_DISK_ALL
214
    else:
215
      mode = constants.REPLACE_DISK_SEC
216

    
217
    mytor = izip(islice(cycle(self.nodes), 2, None),
218
                 self.instances)
219
    for tnode, instance in mytor:
220
      op = opcodes.OpReplaceDisks(instance_name=instance,
221
                                  mode=mode,
222
                                  remote_node=tnode,
223
                                  disks=["sda", "sdb"])
224
      Log("- Replace secondary (%s) for instance %s" % (mode, instance))
225
      self.ExecOp(op)
226

    
227
  def Failover(self):
228
    """Failover the instances."""
229

    
230
    for instance in self.instances:
231
      op = opcodes.OpFailoverInstance(instance_name=instance,
232
                                      ignore_consistency=False)
233

    
234
      Log("- Failover instance %s" % (instance))
235
      self.ExecOp(op)
236

    
237
  def StopStart(self):
238
    """Stop/start the instances."""
239
    for instance in self.instances:
240
      op = opcodes.OpShutdownInstance(instance_name=instance)
241
      Log("- Shutdown instance %s" % instance)
242
      self.ExecOp(op)
243
      op = opcodes.OpStartupInstance(instance_name=instance, force=False)
244
      Log("- Start instance %s" % instance)
245
      self.ExecOp(op)
246

    
247
  def Remove(self):
248
    """Remove the instances."""
249
    for instance in self.to_rem:
250
      op = opcodes.OpRemoveInstance(instance_name=instance)
251
      Log("- Remove instance %s" % instance)
252
      self.ExecOp(op)
253

    
254
  def BurninCluster(self):
255
    """Test a cluster intensively.
256

    
257
    This will create instances and then start/stop/failover them.
258
    It is safe for existing instances but could impact performance.
259

    
260
    """
261

    
262
    opts = self.opts
263

    
264
    Log("- Testing global parameters")
265

    
266
    if len(self.nodes) == 1 and opts.disk_template != constants.DT_PLAIN:
267
      Log("When one node is available/selected the disk template must"
268
               " be 'plain'")
269
      sys.exit(1)
270

    
271
    has_err = True
272
    try:
273
      self.CreateInstances()
274
      if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
275
        if opts.disk_template == constants.DT_REMOTE_RAID1:
276
          self.ReplaceDisks1R1()
277
        elif opts.disk_template == constants.DT_DRBD8:
278
          self.ReplaceDisks1D8()
279
      if (opts.do_replace2 and len(self.nodes) > 2 and
280
          opts.disk_template in constants.DTS_NET_MIRROR) :
281
        self.ReplaceDisks2()
282

    
283
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
284
        self.Failover()
285

    
286
      self.StopStart()
287
      has_err = False
288
    finally:
289
      if has_err:
290
        Log("Error detected: opcode buffer follows:\n\n")
291
        Log(self.GetFeedbackBuf())
292
        Log("\n\n")
293
      self.Remove()
294

    
295
    return 0
296

    
297
def main():
298
  """Main function"""
299

    
300
  burner = Burner()
301
  try:
302
    utils.Lock('cmd', max_retries=15, debug=True)
303
  except errors.LockError, err:
304
    logger.ToStderr(str(err))
305
    return 1
306
  try:
307
    retval = burner.BurninCluster()
308
  finally:
309
    utils.Unlock('cmd')
310
    utils.LockCleanup()
311
  return retval
312

    
313
if __name__ == "__main__":
314
  main()