Statistics
| Branch: | Tag: | Revision:

root / tools / burnin @ 6852c52f

History | View | Annotate | Download (10 kB)

1 a8083063 Iustin Pop
#!/usr/bin/python
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 175f44c2 Iustin Pop
"""Burnin program"""
5 175f44c2 Iustin Pop
6 a8083063 Iustin Pop
import sys
7 a8083063 Iustin Pop
import optparse
8 175f44c2 Iustin Pop
from itertools import izip, islice, cycle
9 21546b1c Iustin Pop
from cStringIO import StringIO
10 a8083063 Iustin Pop
11 a8083063 Iustin Pop
from ganeti import opcodes
12 a8083063 Iustin Pop
from ganeti import mcpu
13 a8083063 Iustin Pop
from ganeti import constants
14 a8083063 Iustin Pop
from ganeti import cli
15 a8083063 Iustin Pop
from ganeti import logger
16 9f13fc7a Iustin Pop
from ganeti import errors
17 9f13fc7a Iustin Pop
from ganeti import utils
18 a8083063 Iustin Pop
19 9f13fc7a Iustin Pop
USAGE = ("\tburnin -o OS_NAME [options...] instance_name ...")
20 a8083063 Iustin Pop
21 a8083063 Iustin Pop
def Usage():
22 a8083063 Iustin Pop
  """Shows program usage information and exits the program."""
23 a8083063 Iustin Pop
24 a8083063 Iustin Pop
  print >> sys.stderr, "Usage:"
25 a8083063 Iustin Pop
  print >> sys.stderr, USAGE
26 a8083063 Iustin Pop
  sys.exit(2)
27 a8083063 Iustin Pop
28 21546b1c Iustin Pop
def Log(msg):
29 3ecf6786 Iustin Pop
  """Simple function that prints out its argument.
30 3ecf6786 Iustin Pop
31 3ecf6786 Iustin Pop
  """
32 3ecf6786 Iustin Pop
  print msg
33 a8083063 Iustin Pop
34 175f44c2 Iustin Pop
class Burner(object):
35 175f44c2 Iustin Pop
  """Burner class."""
36 175f44c2 Iustin Pop
37 175f44c2 Iustin Pop
  def __init__(self):
38 175f44c2 Iustin Pop
    """Constructor."""
39 21546b1c Iustin Pop
    logger.SetupLogging(debug=False, program="ganeti/burnin")
40 21546b1c Iustin Pop
    self._feed_buf = StringIO()
41 21546b1c Iustin Pop
    self.proc = mcpu.Processor(feedback=self.Feedback)
42 175f44c2 Iustin Pop
    self.nodes = []
43 175f44c2 Iustin Pop
    self.instances = []
44 175f44c2 Iustin Pop
    self.to_rem = []
45 175f44c2 Iustin Pop
    self.opts = None
46 175f44c2 Iustin Pop
    self.ParseOptions()
47 175f44c2 Iustin Pop
    self.GetState()
48 175f44c2 Iustin Pop
49 21546b1c Iustin Pop
  def ClearFeedbackBuf(self):
50 21546b1c Iustin Pop
    """Clear the feedback buffer."""
51 21546b1c Iustin Pop
    self._feed_buf.truncate(0)
52 21546b1c Iustin Pop
53 21546b1c Iustin Pop
  def GetFeedbackBuf(self):
54 21546b1c Iustin Pop
    """Return the contents of the buffer."""
55 21546b1c Iustin Pop
    return self._feed_buf.getvalue()
56 21546b1c Iustin Pop
57 21546b1c Iustin Pop
  def Feedback(self, msg):
58 21546b1c Iustin Pop
    """Acumulate feedback in our buffer."""
59 21546b1c Iustin Pop
    self._feed_buf.write(msg)
60 21546b1c Iustin Pop
    self._feed_buf.write("\n")
61 21546b1c Iustin Pop
62 21546b1c Iustin Pop
  def ExecOp(self, op):
63 21546b1c Iustin Pop
    """Execute an opcode and manage the exec buffer."""
64 21546b1c Iustin Pop
    self.ClearFeedbackBuf()
65 21546b1c Iustin Pop
    return self.proc.ExecOpCode(op)
66 21546b1c Iustin Pop
67 175f44c2 Iustin Pop
  def ParseOptions(self):
68 175f44c2 Iustin Pop
    """Parses the command line options.
69 175f44c2 Iustin Pop
70 175f44c2 Iustin Pop
    In case of command line errors, it will show the usage and exit the
71 175f44c2 Iustin Pop
    program.
72 175f44c2 Iustin Pop
73 175f44c2 Iustin Pop
    """
74 175f44c2 Iustin Pop
75 175f44c2 Iustin Pop
    parser = optparse.OptionParser(usage="\n%s" % USAGE,
76 175f44c2 Iustin Pop
                                   version="%%prog (ganeti) %s" %
77 175f44c2 Iustin Pop
                                   constants.RELEASE_VERSION,
78 175f44c2 Iustin Pop
                                   option_class=cli.CliOption)
79 175f44c2 Iustin Pop
80 175f44c2 Iustin Pop
    parser.add_option("-o", "--os", dest="os", default=None,
81 175f44c2 Iustin Pop
                      help="OS to use during burnin",
82 175f44c2 Iustin Pop
                      metavar="<OS>")
83 175f44c2 Iustin Pop
    parser.add_option("--os-size", dest="os_size", help="Disk size",
84 175f44c2 Iustin Pop
                      default=4 * 1024, type="unit", metavar="<size>")
85 175f44c2 Iustin Pop
    parser.add_option("--swap-size", dest="swap_size", help="Swap size",
86 175f44c2 Iustin Pop
                      default=4 * 1024, type="unit", metavar="<size>")
87 175f44c2 Iustin Pop
    parser.add_option("-v", "--verbose",
88 175f44c2 Iustin Pop
                      action="store_true", dest="verbose", default=False,
89 175f44c2 Iustin Pop
                      help="print command execution messages to stdout")
90 175f44c2 Iustin Pop
    parser.add_option("--no-replace1", dest="do_replace1",
91 175f44c2 Iustin Pop
                      help="Skip disk replacement with the same secondary",
92 175f44c2 Iustin Pop
                      action="store_false", default=True)
93 175f44c2 Iustin Pop
    parser.add_option("--no-replace2", dest="do_replace2",
94 175f44c2 Iustin Pop
                      help="Skip disk replacement with a different secondary",
95 175f44c2 Iustin Pop
                      action="store_false", default=True)
96 175f44c2 Iustin Pop
    parser.add_option("--no-failover", dest="do_failover",
97 175f44c2 Iustin Pop
                      help="Skip instance failovers", action="store_false",
98 175f44c2 Iustin Pop
                      default=True)
99 175f44c2 Iustin Pop
    parser.add_option("-t", "--disk-template", dest="disk_template",
100 175f44c2 Iustin Pop
                      choices=("remote_raid1", "drbd8"),
101 175f44c2 Iustin Pop
                      default="remote_raid1",
102 175f44c2 Iustin Pop
                      help="Template type for network mirroring (remote_raid1"
103 175f44c2 Iustin Pop
                      " or drbd8) [remote_raid1]")
104 175f44c2 Iustin Pop
    parser.add_option("-n", "--nodes", dest="nodes", default="",
105 175f44c2 Iustin Pop
                      help="Comma separated list of nodes to perform"
106 175f44c2 Iustin Pop
                      " the burnin on (defaults to all nodes)")
107 175f44c2 Iustin Pop
108 175f44c2 Iustin Pop
    options, args = parser.parse_args()
109 175f44c2 Iustin Pop
    if len(args) < 1 or options.os is None:
110 175f44c2 Iustin Pop
      Usage()
111 175f44c2 Iustin Pop
112 175f44c2 Iustin Pop
    if options.disk_template == "plain":
113 175f44c2 Iustin Pop
      disk_template = constants.DT_PLAIN
114 175f44c2 Iustin Pop
    elif options.disk_template == "remote_raid1":
115 175f44c2 Iustin Pop
      disk_template = constants.DT_REMOTE_RAID1
116 175f44c2 Iustin Pop
    elif options.disk_template == "drbd8":
117 175f44c2 Iustin Pop
      disk_template = constants.DT_DRBD8
118 175f44c2 Iustin Pop
    else:
119 21546b1c Iustin Pop
      Log("Unknown disk template '%s'" % options.disk_template)
120 175f44c2 Iustin Pop
      sys.exit(1)
121 175f44c2 Iustin Pop
122 175f44c2 Iustin Pop
    options.disk_template = disk_template
123 175f44c2 Iustin Pop
    self.opts = options
124 175f44c2 Iustin Pop
    self.instances = args
125 175f44c2 Iustin Pop
126 175f44c2 Iustin Pop
  def GetState(self):
127 175f44c2 Iustin Pop
    """Read the cluster state from the config."""
128 175f44c2 Iustin Pop
    if self.opts.nodes:
129 175f44c2 Iustin Pop
      names = self.opts.nodes.split(",")
130 175f44c2 Iustin Pop
    else:
131 175f44c2 Iustin Pop
      names = []
132 175f44c2 Iustin Pop
    try:
133 175f44c2 Iustin Pop
      op = opcodes.OpQueryNodes(output_fields=["name"], names=names)
134 21546b1c Iustin Pop
      result = self.ExecOp(op)
135 175f44c2 Iustin Pop
    except errors.GenericError, err:
136 175f44c2 Iustin Pop
      err_code, msg = cli.FormatError(err)
137 21546b1c Iustin Pop
      Log(msg)
138 175f44c2 Iustin Pop
      sys.exit(err_code)
139 175f44c2 Iustin Pop
    self.nodes = [data[0] for data in result]
140 175f44c2 Iustin Pop
141 21546b1c Iustin Pop
    result = self.ExecOp(opcodes.OpDiagnoseOS())
142 175f44c2 Iustin Pop
143 175f44c2 Iustin Pop
    if not result:
144 21546b1c Iustin Pop
      Log("Can't get the OS list")
145 175f44c2 Iustin Pop
      sys.exit(1)
146 175f44c2 Iustin Pop
147 175f44c2 Iustin Pop
    # filter non-valid OS-es
148 175f44c2 Iustin Pop
    oses = {}
149 175f44c2 Iustin Pop
    for node_name in result:
150 175f44c2 Iustin Pop
      oses[node_name] = [obj for obj in result[node_name] if obj]
151 175f44c2 Iustin Pop
152 175f44c2 Iustin Pop
    fnode = oses.keys()[0]
153 175f44c2 Iustin Pop
    os_set = set([os_inst.name for os_inst in oses[fnode]])
154 175f44c2 Iustin Pop
    del oses[fnode]
155 175f44c2 Iustin Pop
    for node in oses:
156 175f44c2 Iustin Pop
      os_set &= set([os_inst.name for os_inst in oses[node]])
157 175f44c2 Iustin Pop
158 175f44c2 Iustin Pop
    if self.opts.os not in os_set:
159 21546b1c Iustin Pop
      Log("OS '%s' not found" % self.opts.os)
160 175f44c2 Iustin Pop
      sys.exit(1)
161 175f44c2 Iustin Pop
162 175f44c2 Iustin Pop
  def CreateInstances(self):
163 175f44c2 Iustin Pop
    """Create the given instances.
164 175f44c2 Iustin Pop
165 175f44c2 Iustin Pop
    """
166 175f44c2 Iustin Pop
    self.to_rem = []
167 175f44c2 Iustin Pop
    mytor = izip(cycle(self.nodes),
168 175f44c2 Iustin Pop
                 islice(cycle(self.nodes), 1, None),
169 175f44c2 Iustin Pop
                 self.instances)
170 175f44c2 Iustin Pop
    for pnode, snode, instance in mytor:
171 175f44c2 Iustin Pop
      op = opcodes.OpCreateInstance(instance_name=instance,
172 175f44c2 Iustin Pop
                                    mem_size=128,
173 175f44c2 Iustin Pop
                                    disk_size=self.opts.os_size,
174 175f44c2 Iustin Pop
                                    swap_size=self.opts.swap_size,
175 175f44c2 Iustin Pop
                                    disk_template=self.opts.disk_template,
176 a8083063 Iustin Pop
                                    mode=constants.INSTANCE_CREATE,
177 175f44c2 Iustin Pop
                                    os_type=self.opts.os,
178 175f44c2 Iustin Pop
                                    pnode=pnode,
179 175f44c2 Iustin Pop
                                    snode=snode,
180 175f44c2 Iustin Pop
                                    vcpus=1,
181 a8083063 Iustin Pop
                                    start=True,
182 e9f745aa Iustin Pop
                                    ip_check=True,
183 a8083063 Iustin Pop
                                    wait_for_sync=True)
184 21546b1c Iustin Pop
      Log("- Add instance %s on node %s" % (instance, pnode))
185 21546b1c Iustin Pop
      self.ExecOp(op)
186 175f44c2 Iustin Pop
      self.to_rem.append(instance)
187 175f44c2 Iustin Pop
188 175f44c2 Iustin Pop
  def ReplaceDisks1R1(self):
189 175f44c2 Iustin Pop
    """Replace disks with the same secondary for rr1."""
190 175f44c2 Iustin Pop
    # replace all, both disks
191 175f44c2 Iustin Pop
    for instance in self.instances:
192 175f44c2 Iustin Pop
      op = opcodes.OpReplaceDisks(instance_name=instance,
193 175f44c2 Iustin Pop
                                  remote_node=None,
194 175f44c2 Iustin Pop
                                  mode=constants.REPLACE_DISK_ALL,
195 175f44c2 Iustin Pop
                                  disks=["sda", "sdb"])
196 175f44c2 Iustin Pop
197 21546b1c Iustin Pop
      Log("- Replace disks for instance %s" % (instance))
198 21546b1c Iustin Pop
      self.ExecOp(op)
199 175f44c2 Iustin Pop
200 175f44c2 Iustin Pop
  def ReplaceDisks1D8(self):
201 175f44c2 Iustin Pop
    """Replace disks on primary and secondary for drbd8."""
202 175f44c2 Iustin Pop
    for instance in self.instances:
203 175f44c2 Iustin Pop
      for mode in constants.REPLACE_DISK_SEC, constants.REPLACE_DISK_PRI:
204 175f44c2 Iustin Pop
        op = opcodes.OpReplaceDisks(instance_name=instance,
205 175f44c2 Iustin Pop
                                    mode=mode,
206 175f44c2 Iustin Pop
                                    disks=["sda", "sdb"])
207 21546b1c Iustin Pop
        Log("- Replace disks (%s) for instance %s" % (mode, instance))
208 21546b1c Iustin Pop
        self.ExecOp(op)
209 175f44c2 Iustin Pop
210 175f44c2 Iustin Pop
  def ReplaceDisks2(self):
211 175f44c2 Iustin Pop
    """Replace secondary node."""
212 175f44c2 Iustin Pop
    if self.opts.disk_template == constants.DT_REMOTE_RAID1:
213 175f44c2 Iustin Pop
      mode = constants.REPLACE_DISK_ALL
214 175f44c2 Iustin Pop
    else:
215 175f44c2 Iustin Pop
      mode = constants.REPLACE_DISK_SEC
216 175f44c2 Iustin Pop
217 175f44c2 Iustin Pop
    mytor = izip(islice(cycle(self.nodes), 2, None),
218 175f44c2 Iustin Pop
                 self.instances)
219 175f44c2 Iustin Pop
    for tnode, instance in mytor:
220 175f44c2 Iustin Pop
      op = opcodes.OpReplaceDisks(instance_name=instance,
221 175f44c2 Iustin Pop
                                  mode=mode,
222 175f44c2 Iustin Pop
                                  remote_node=tnode,
223 175f44c2 Iustin Pop
                                  disks=["sda", "sdb"])
224 21546b1c Iustin Pop
      Log("- Replace secondary (%s) for instance %s" % (mode, instance))
225 21546b1c Iustin Pop
      self.ExecOp(op)
226 175f44c2 Iustin Pop
227 175f44c2 Iustin Pop
  def Failover(self):
228 175f44c2 Iustin Pop
    """Failover the instances."""
229 175f44c2 Iustin Pop
230 175f44c2 Iustin Pop
    for instance in self.instances:
231 175f44c2 Iustin Pop
      op = opcodes.OpFailoverInstance(instance_name=instance,
232 175f44c2 Iustin Pop
                                      ignore_consistency=False)
233 175f44c2 Iustin Pop
234 21546b1c Iustin Pop
      Log("- Failover instance %s" % (instance))
235 21546b1c Iustin Pop
      self.ExecOp(op)
236 175f44c2 Iustin Pop
237 175f44c2 Iustin Pop
  def StopStart(self):
238 175f44c2 Iustin Pop
    """Stop/start the instances."""
239 175f44c2 Iustin Pop
    for instance in self.instances:
240 175f44c2 Iustin Pop
      op = opcodes.OpShutdownInstance(instance_name=instance)
241 21546b1c Iustin Pop
      Log("- Shutdown instance %s" % instance)
242 21546b1c Iustin Pop
      self.ExecOp(op)
243 175f44c2 Iustin Pop
      op = opcodes.OpStartupInstance(instance_name=instance, force=False)
244 21546b1c Iustin Pop
      Log("- Start instance %s" % instance)
245 21546b1c Iustin Pop
      self.ExecOp(op)
246 175f44c2 Iustin Pop
247 175f44c2 Iustin Pop
  def Remove(self):
248 175f44c2 Iustin Pop
    """Remove the instances."""
249 175f44c2 Iustin Pop
    for instance in self.to_rem:
250 175f44c2 Iustin Pop
      op = opcodes.OpRemoveInstance(instance_name=instance)
251 21546b1c Iustin Pop
      Log("- Remove instance %s" % instance)
252 21546b1c Iustin Pop
      self.ExecOp(op)
253 175f44c2 Iustin Pop
254 175f44c2 Iustin Pop
  def BurninCluster(self):
255 175f44c2 Iustin Pop
    """Test a cluster intensively.
256 175f44c2 Iustin Pop
257 175f44c2 Iustin Pop
    This will create instances and then start/stop/failover them.
258 175f44c2 Iustin Pop
    It is safe for existing instances but could impact performance.
259 175f44c2 Iustin Pop
260 175f44c2 Iustin Pop
    """
261 175f44c2 Iustin Pop
262 175f44c2 Iustin Pop
    opts = self.opts
263 175f44c2 Iustin Pop
264 21546b1c Iustin Pop
    Log("- Testing global parameters")
265 175f44c2 Iustin Pop
266 175f44c2 Iustin Pop
    if len(self.nodes) == 1 and opts.disk_template != constants.DT_PLAIN:
267 21546b1c Iustin Pop
      Log("When one node is available/selected the disk template must"
268 175f44c2 Iustin Pop
               " be 'plain'")
269 175f44c2 Iustin Pop
      sys.exit(1)
270 175f44c2 Iustin Pop
271 21546b1c Iustin Pop
    has_err = True
272 175f44c2 Iustin Pop
    try:
273 175f44c2 Iustin Pop
      self.CreateInstances()
274 175f44c2 Iustin Pop
      if opts.do_replace1 and opts.disk_template in constants.DTS_NET_MIRROR:
275 175f44c2 Iustin Pop
        if opts.disk_template == constants.DT_REMOTE_RAID1:
276 175f44c2 Iustin Pop
          self.ReplaceDisks1R1()
277 175f44c2 Iustin Pop
        elif opts.disk_template == constants.DT_DRBD8:
278 175f44c2 Iustin Pop
          self.ReplaceDisks1D8()
279 175f44c2 Iustin Pop
      if (opts.do_replace2 and len(self.nodes) > 2 and
280 175f44c2 Iustin Pop
          opts.disk_template in constants.DTS_NET_MIRROR) :
281 175f44c2 Iustin Pop
        self.ReplaceDisks2()
282 175f44c2 Iustin Pop
283 175f44c2 Iustin Pop
      if opts.do_failover and opts.disk_template in constants.DTS_NET_MIRROR:
284 175f44c2 Iustin Pop
        self.Failover()
285 175f44c2 Iustin Pop
286 175f44c2 Iustin Pop
      self.StopStart()
287 21546b1c Iustin Pop
      has_err = False
288 175f44c2 Iustin Pop
    finally:
289 21546b1c Iustin Pop
      if has_err:
290 21546b1c Iustin Pop
        Log("Error detected: opcode buffer follows:\n\n")
291 21546b1c Iustin Pop
        Log(self.GetFeedbackBuf())
292 21546b1c Iustin Pop
        Log("\n\n")
293 175f44c2 Iustin Pop
      self.Remove()
294 175f44c2 Iustin Pop
295 175f44c2 Iustin Pop
    return 0
296 a8083063 Iustin Pop
297 a8083063 Iustin Pop
def main():
298 3ecf6786 Iustin Pop
  """Main function"""
299 3ecf6786 Iustin Pop
300 175f44c2 Iustin Pop
  burner = Burner()
301 3ecf6786 Iustin Pop
  try:
302 3ecf6786 Iustin Pop
    utils.Lock('cmd', max_retries=15, debug=True)
303 3ecf6786 Iustin Pop
  except errors.LockError, err:
304 3ecf6786 Iustin Pop
    logger.ToStderr(str(err))
305 3ecf6786 Iustin Pop
    return 1
306 3ecf6786 Iustin Pop
  try:
307 175f44c2 Iustin Pop
    retval = burner.BurninCluster()
308 3ecf6786 Iustin Pop
  finally:
309 3ecf6786 Iustin Pop
    utils.Unlock('cmd')
310 3ecf6786 Iustin Pop
    utils.LockCleanup()
311 3ecf6786 Iustin Pop
  return retval
312 a8083063 Iustin Pop
313 a8083063 Iustin Pop
if __name__ == "__main__":
314 3ecf6786 Iustin Pop
  main()