Statistics
| Branch: | Tag: | Revision:

root / testing / ganeti.qa.py @ 6f11f250

History | View | Annotate | Download (17.4 kB)

1 a8083063 Iustin Pop
#!/usr/bin/python
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 Google Inc.
5 a8083063 Iustin Pop
#
6 a8083063 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 a8083063 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 a8083063 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 a8083063 Iustin Pop
# (at your option) any later version.
10 a8083063 Iustin Pop
#
11 a8083063 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 a8083063 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a8083063 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a8083063 Iustin Pop
# General Public License for more details.
15 a8083063 Iustin Pop
#
16 a8083063 Iustin Pop
# You should have received a copy of the GNU General Public License
17 a8083063 Iustin Pop
# along with this program; if not, write to the Free Software
18 a8083063 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a8083063 Iustin Pop
# 02110-1301, USA.
20 a8083063 Iustin Pop
21 a8083063 Iustin Pop
22 a8083063 Iustin Pop
"""Script for doing Q&A on Ganeti"""
23 a8083063 Iustin Pop
24 a8083063 Iustin Pop
import os
25 a8083063 Iustin Pop
import re
26 a8083063 Iustin Pop
import sys
27 a8083063 Iustin Pop
import yaml
28 a8083063 Iustin Pop
import time
29 a8083063 Iustin Pop
30 a8083063 Iustin Pop
from datetime import datetime
31 a8083063 Iustin Pop
from optparse import OptionParser
32 a8083063 Iustin Pop
33 a8083063 Iustin Pop
# I want more flexibility for testing over SSH, therefore I'm not using
34 a8083063 Iustin Pop
# Ganeti's ssh module.
35 a8083063 Iustin Pop
import subprocess
36 a8083063 Iustin Pop
37 a8083063 Iustin Pop
from ganeti import utils
38 a8083063 Iustin Pop
from ganeti import constants
39 a8083063 Iustin Pop
40 a8083063 Iustin Pop
# {{{ Global variables
41 a8083063 Iustin Pop
cfg = None
42 a8083063 Iustin Pop
options = None
43 a8083063 Iustin Pop
# }}}
44 a8083063 Iustin Pop
45 a8083063 Iustin Pop
# {{{ Errors
46 a8083063 Iustin Pop
class Error(Exception):
47 a8083063 Iustin Pop
  """An error occurred during Q&A testing.
48 a8083063 Iustin Pop

49 a8083063 Iustin Pop
  """
50 a8083063 Iustin Pop
  pass
51 a8083063 Iustin Pop
52 a8083063 Iustin Pop
53 a8083063 Iustin Pop
class OutOfNodesError(Error):
54 a8083063 Iustin Pop
  """Out of nodes.
55 a8083063 Iustin Pop

56 a8083063 Iustin Pop
  """
57 a8083063 Iustin Pop
  pass
58 a8083063 Iustin Pop
59 a8083063 Iustin Pop
60 a8083063 Iustin Pop
class OutOfInstancesError(Error):
61 a8083063 Iustin Pop
  """Out of instances.
62 a8083063 Iustin Pop

63 a8083063 Iustin Pop
  """
64 a8083063 Iustin Pop
  pass
65 a8083063 Iustin Pop
# }}}
66 a8083063 Iustin Pop
67 a8083063 Iustin Pop
# {{{ Utilities
68 a8083063 Iustin Pop
def TestEnabled(test):
69 a8083063 Iustin Pop
  """Returns True if the given test is enabled."""
70 a8083063 Iustin Pop
  return cfg.get('tests', {}).get(test, False)
71 a8083063 Iustin Pop
72 a8083063 Iustin Pop
73 a8083063 Iustin Pop
def RunTest(callable, *args):
74 a8083063 Iustin Pop
  """Runs a test after printing a header.
75 a8083063 Iustin Pop

76 a8083063 Iustin Pop
  """
77 a8083063 Iustin Pop
  if callable.__doc__:
78 a8083063 Iustin Pop
    desc = callable.__doc__.splitlines()[0].strip()
79 a8083063 Iustin Pop
  else:
80 a8083063 Iustin Pop
    desc = '%r' % callable
81 a8083063 Iustin Pop
82 a8083063 Iustin Pop
  now = str(datetime.now())
83 a8083063 Iustin Pop
84 a8083063 Iustin Pop
  print
85 a8083063 Iustin Pop
  print '---', now, ('-' * (55 - len(now)))
86 a8083063 Iustin Pop
  print desc
87 a8083063 Iustin Pop
  print '-' * 60
88 a8083063 Iustin Pop
89 a8083063 Iustin Pop
  return callable(*args)
90 a8083063 Iustin Pop
91 a8083063 Iustin Pop
92 a8083063 Iustin Pop
def AssertEqual(first, second, msg=None):
93 a8083063 Iustin Pop
  """Raises an error when values aren't equal.
94 a8083063 Iustin Pop

95 a8083063 Iustin Pop
  """
96 a8083063 Iustin Pop
  if not first == second:
97 a8083063 Iustin Pop
    raise Error, (msg or '%r == %r' % (first, second))
98 a8083063 Iustin Pop
99 a8083063 Iustin Pop
100 a8083063 Iustin Pop
def GetSSHCommand(node, cmd, strict=True):
101 a8083063 Iustin Pop
  """Builds SSH command to be executed.
102 a8083063 Iustin Pop

103 a8083063 Iustin Pop
  """
104 a8083063 Iustin Pop
  args = [ 'ssh', '-oEscapeChar=none', '-oBatchMode=yes', '-l', 'root' ]
105 a8083063 Iustin Pop
106 a8083063 Iustin Pop
  if strict:
107 a8083063 Iustin Pop
    tmp = 'yes'
108 a8083063 Iustin Pop
  else:
109 a8083063 Iustin Pop
    tmp = 'no'
110 a8083063 Iustin Pop
  args.append('-oStrictHostKeyChecking=%s' % tmp)
111 a8083063 Iustin Pop
  args.append(node)
112 a8083063 Iustin Pop
113 a8083063 Iustin Pop
  if options.dry_run:
114 a8083063 Iustin Pop
    prefix = 'exit 0; '
115 a8083063 Iustin Pop
  else:
116 a8083063 Iustin Pop
    prefix = ''
117 a8083063 Iustin Pop
118 a8083063 Iustin Pop
  args.append(prefix + cmd)
119 a8083063 Iustin Pop
120 a8083063 Iustin Pop
  if options.verbose:
121 a8083063 Iustin Pop
    print 'SSH:', utils.ShellQuoteArgs(args)
122 a8083063 Iustin Pop
123 a8083063 Iustin Pop
  return args
124 a8083063 Iustin Pop
125 a8083063 Iustin Pop
126 a8083063 Iustin Pop
def StartSSH(node, cmd, strict=True):
127 a8083063 Iustin Pop
  """Starts SSH.
128 a8083063 Iustin Pop

129 a8083063 Iustin Pop
  """
130 a8083063 Iustin Pop
  args = GetSSHCommand(node, cmd, strict=strict)
131 a8083063 Iustin Pop
  return subprocess.Popen(args, shell=False)
132 a8083063 Iustin Pop
133 a8083063 Iustin Pop
134 a8083063 Iustin Pop
def UploadFile(node, file):
135 a8083063 Iustin Pop
  """Uploads a file to a node and returns the filename.
136 a8083063 Iustin Pop

137 a8083063 Iustin Pop
  Caller needs to remove the file when it's not needed anymore.
138 a8083063 Iustin Pop
  """
139 a8083063 Iustin Pop
  if os.stat(file).st_mode & 0100:
140 a8083063 Iustin Pop
    mode = '0700'
141 a8083063 Iustin Pop
  else:
142 a8083063 Iustin Pop
    mode = '0600'
143 a8083063 Iustin Pop
144 a8083063 Iustin Pop
  cmd = ('tmp=$(tempfile --mode %s --prefix gnt) && '
145 a8083063 Iustin Pop
         '[[ -f "${tmp}" ]] && '
146 a8083063 Iustin Pop
         'cat > "${tmp}" && '
147 a8083063 Iustin Pop
         'echo "${tmp}"') % mode
148 a8083063 Iustin Pop
149 a8083063 Iustin Pop
  f = open(file, 'r')
150 a8083063 Iustin Pop
  try:
151 a8083063 Iustin Pop
    p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
152 a8083063 Iustin Pop
                         stdout=subprocess.PIPE)
153 a8083063 Iustin Pop
    AssertEqual(p.wait(), 0)
154 a8083063 Iustin Pop
155 a8083063 Iustin Pop
    name = p.stdout.read().strip()
156 a8083063 Iustin Pop
157 a8083063 Iustin Pop
    return name
158 a8083063 Iustin Pop
  finally:
159 a8083063 Iustin Pop
    f.close()
160 a8083063 Iustin Pop
# }}}
161 a8083063 Iustin Pop
162 a8083063 Iustin Pop
# {{{ Config helpers
163 a8083063 Iustin Pop
def GetMasterNode():
164 a8083063 Iustin Pop
  return cfg['nodes'][0]
165 a8083063 Iustin Pop
166 a8083063 Iustin Pop
167 a8083063 Iustin Pop
def AcquireInstance():
168 a8083063 Iustin Pop
  """Returns an instance which isn't in use.
169 a8083063 Iustin Pop

170 a8083063 Iustin Pop
  """
171 a8083063 Iustin Pop
  # Filter out unwanted instances
172 a8083063 Iustin Pop
  tmp_flt = lambda inst: not inst.get('_used', False)
173 a8083063 Iustin Pop
  instances = filter(tmp_flt, cfg['instances'])
174 a8083063 Iustin Pop
  del tmp_flt
175 a8083063 Iustin Pop
176 a8083063 Iustin Pop
  if len(instances) == 0:
177 a8083063 Iustin Pop
    raise OutOfInstancesError, ("No instances left")
178 a8083063 Iustin Pop
179 a8083063 Iustin Pop
  inst = instances[0]
180 a8083063 Iustin Pop
  inst['_used'] = True
181 a8083063 Iustin Pop
  return inst
182 a8083063 Iustin Pop
183 a8083063 Iustin Pop
184 a8083063 Iustin Pop
def ReleaseInstance(inst):
185 a8083063 Iustin Pop
  inst['_used'] = False
186 a8083063 Iustin Pop
187 a8083063 Iustin Pop
188 a8083063 Iustin Pop
def AcquireNode(exclude=None):
189 a8083063 Iustin Pop
  """Returns the least used node.
190 a8083063 Iustin Pop

191 a8083063 Iustin Pop
  """
192 a8083063 Iustin Pop
  master = GetMasterNode()
193 a8083063 Iustin Pop
194 a8083063 Iustin Pop
  # Filter out unwanted nodes
195 a8083063 Iustin Pop
  # TODO: Maybe combine filters
196 a8083063 Iustin Pop
  if exclude is None:
197 a8083063 Iustin Pop
    nodes = cfg['nodes'][:]
198 a8083063 Iustin Pop
  else:
199 a8083063 Iustin Pop
    nodes = filter(lambda node: node != exclude, cfg['nodes'])
200 a8083063 Iustin Pop
201 a8083063 Iustin Pop
  tmp_flt = lambda node: node.get('_added', False) or node == master
202 a8083063 Iustin Pop
  nodes = filter(tmp_flt, nodes)
203 a8083063 Iustin Pop
  del tmp_flt
204 a8083063 Iustin Pop
205 a8083063 Iustin Pop
  if len(nodes) == 0:
206 a8083063 Iustin Pop
    raise OutOfNodesError, ("No nodes left")
207 a8083063 Iustin Pop
208 a8083063 Iustin Pop
  # Get node with least number of uses
209 a8083063 Iustin Pop
  def compare(a, b):
210 a8083063 Iustin Pop
    result = cmp(a.get('_count', 0), b.get('_count', 0))
211 a8083063 Iustin Pop
    if result == 0:
212 a8083063 Iustin Pop
      result = cmp(a['primary'], b['primary'])
213 a8083063 Iustin Pop
    return result
214 a8083063 Iustin Pop
215 a8083063 Iustin Pop
  nodes.sort(cmp=compare)
216 a8083063 Iustin Pop
217 a8083063 Iustin Pop
  node = nodes[0]
218 a8083063 Iustin Pop
  node['_count'] = node.get('_count', 0) + 1
219 a8083063 Iustin Pop
  return node
220 a8083063 Iustin Pop
221 a8083063 Iustin Pop
222 a8083063 Iustin Pop
def ReleaseNode(node):
223 a8083063 Iustin Pop
  node['_count'] = node.get('_count', 0) - 1
224 a8083063 Iustin Pop
# }}}
225 a8083063 Iustin Pop
226 a8083063 Iustin Pop
# {{{ Environment tests
227 a8083063 Iustin Pop
def TestConfig():
228 a8083063 Iustin Pop
  """Test configuration for sanity.
229 a8083063 Iustin Pop

230 a8083063 Iustin Pop
  """
231 a8083063 Iustin Pop
  if len(cfg['nodes']) < 1:
232 a8083063 Iustin Pop
    raise Error, ("Need at least one node")
233 a8083063 Iustin Pop
  if len(cfg['instances']) < 1:
234 a8083063 Iustin Pop
    raise Error, ("Need at least one instance")
235 a8083063 Iustin Pop
  # TODO: Add more checks
236 a8083063 Iustin Pop
237 a8083063 Iustin Pop
238 a8083063 Iustin Pop
def TestSshConnection():
239 a8083063 Iustin Pop
  """Test SSH connection.
240 a8083063 Iustin Pop

241 a8083063 Iustin Pop
  """
242 a8083063 Iustin Pop
  for node in cfg['nodes']:
243 a8083063 Iustin Pop
    AssertEqual(StartSSH(node['primary'], 'exit').wait(), 0)
244 a8083063 Iustin Pop
245 a8083063 Iustin Pop
246 a8083063 Iustin Pop
def TestGanetiCommands():
247 a8083063 Iustin Pop
  """Test availibility of Ganeti commands.
248 a8083063 Iustin Pop

249 a8083063 Iustin Pop
  """
250 a8083063 Iustin Pop
  cmds = ( ['gnt-cluster', '--version'],
251 a8083063 Iustin Pop
           ['gnt-os', '--version'],
252 a8083063 Iustin Pop
           ['gnt-node', '--version'],
253 a8083063 Iustin Pop
           ['gnt-instance', '--version'],
254 a8083063 Iustin Pop
           ['gnt-backup', '--version'],
255 a8083063 Iustin Pop
           ['ganeti-noded', '--version'],
256 a8083063 Iustin Pop
           ['ganeti-watcher', '--version'] )
257 a8083063 Iustin Pop
258 a8083063 Iustin Pop
  cmd = ' && '.join(map(utils.ShellQuoteArgs, cmds))
259 a8083063 Iustin Pop
260 a8083063 Iustin Pop
  for node in cfg['nodes']:
261 a8083063 Iustin Pop
    AssertEqual(StartSSH(node['primary'], cmd).wait(), 0)
262 a8083063 Iustin Pop
263 a8083063 Iustin Pop
264 a8083063 Iustin Pop
def TestIcmpPing():
265 a8083063 Iustin Pop
  """ICMP ping each node.
266 a8083063 Iustin Pop

267 a8083063 Iustin Pop
  """
268 a8083063 Iustin Pop
  for node in cfg['nodes']:
269 a8083063 Iustin Pop
    check = []
270 a8083063 Iustin Pop
    for i in cfg['nodes']:
271 a8083063 Iustin Pop
      check.append(i['primary'])
272 a8083063 Iustin Pop
      if i.has_key('secondary'):
273 a8083063 Iustin Pop
        check.append(i['secondary'])
274 a8083063 Iustin Pop
275 a8083063 Iustin Pop
    ping = lambda ip: utils.ShellQuoteArgs(['ping', '-w', '3', '-c', '1', ip])
276 a8083063 Iustin Pop
    cmd = ' && '.join(map(ping, check))
277 a8083063 Iustin Pop
278 a8083063 Iustin Pop
    AssertEqual(StartSSH(node['primary'], cmd).wait(), 0)
279 a8083063 Iustin Pop
# }}}
280 a8083063 Iustin Pop
281 a8083063 Iustin Pop
# {{{ Cluster tests
282 a8083063 Iustin Pop
def TestClusterInit():
283 a8083063 Iustin Pop
  """gnt-cluster init"""
284 a8083063 Iustin Pop
  master = GetMasterNode()
285 a8083063 Iustin Pop
286 a8083063 Iustin Pop
  cmd = ['gnt-cluster', 'init']
287 a8083063 Iustin Pop
  if master.get('secondary', None):
288 a8083063 Iustin Pop
    cmd.append('--secondary-ip=%s' % master['secondary'])
289 6f11f250 Michael Hanselmann
  if cfg.get('bridge', None):
290 6f11f250 Michael Hanselmann
    cmd.append('--bridge=%s' % cfg['bridge'])
291 a8083063 Iustin Pop
  cmd.append(cfg['name'])
292 a8083063 Iustin Pop
293 a8083063 Iustin Pop
  AssertEqual(StartSSH(master['primary'],
294 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
295 a8083063 Iustin Pop
296 a8083063 Iustin Pop
297 a8083063 Iustin Pop
def TestClusterVerify():
298 a8083063 Iustin Pop
  """gnt-cluster verify"""
299 a8083063 Iustin Pop
  cmd = ['gnt-cluster', 'verify']
300 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
301 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
302 a8083063 Iustin Pop
303 a8083063 Iustin Pop
304 a8083063 Iustin Pop
def TestClusterInfo():
305 a8083063 Iustin Pop
  """gnt-cluster info"""
306 a8083063 Iustin Pop
  cmd = ['gnt-cluster', 'info']
307 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
308 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
309 a8083063 Iustin Pop
310 a8083063 Iustin Pop
311 a8083063 Iustin Pop
def TestClusterBurnin():
312 a8083063 Iustin Pop
  """Burnin"""
313 a8083063 Iustin Pop
  master = GetMasterNode()
314 a8083063 Iustin Pop
315 a8083063 Iustin Pop
  # Get as many instances as we need
316 a8083063 Iustin Pop
  instances = []
317 a8083063 Iustin Pop
  try:
318 a8083063 Iustin Pop
    for _ in xrange(0, cfg.get('options', {}).get('burnin-instances', 1)):
319 a8083063 Iustin Pop
      instances.append(AcquireInstance())
320 a8083063 Iustin Pop
  except OutOfInstancesError:
321 a8083063 Iustin Pop
    print "Not enough instances, continuing anyway."
322 a8083063 Iustin Pop
323 a8083063 Iustin Pop
  if len(instances) < 1:
324 a8083063 Iustin Pop
    raise Error, ("Burnin needs at least one instance")
325 a8083063 Iustin Pop
326 a8083063 Iustin Pop
  # Run burnin
327 a8083063 Iustin Pop
  try:
328 a8083063 Iustin Pop
    script = UploadFile(master['primary'], '../tools/burnin')
329 a8083063 Iustin Pop
    try:
330 a8083063 Iustin Pop
      cmd = [script, '--os=%s' % cfg['os']]
331 a8083063 Iustin Pop
      cmd += map(lambda inst: inst['name'], instances)
332 a8083063 Iustin Pop
      AssertEqual(StartSSH(master['primary'],
333 a8083063 Iustin Pop
                           utils.ShellQuoteArgs(cmd)).wait(), 0)
334 a8083063 Iustin Pop
    finally:
335 a8083063 Iustin Pop
      cmd = ['rm', '-f', script]
336 a8083063 Iustin Pop
      AssertEqual(StartSSH(master['primary'],
337 a8083063 Iustin Pop
                           utils.ShellQuoteArgs(cmd)).wait(), 0)
338 a8083063 Iustin Pop
  finally:
339 a8083063 Iustin Pop
    for inst in instances:
340 a8083063 Iustin Pop
      ReleaseInstance(inst)
341 a8083063 Iustin Pop
342 a8083063 Iustin Pop
343 a8083063 Iustin Pop
def TestClusterMasterFailover():
344 a8083063 Iustin Pop
  """gnt-cluster masterfailover"""
345 a8083063 Iustin Pop
  master = GetMasterNode()
346 a8083063 Iustin Pop
347 a8083063 Iustin Pop
  failovermaster = AcquireNode(exclude=master)
348 a8083063 Iustin Pop
  try:
349 a8083063 Iustin Pop
    cmd = ['gnt-cluster', 'masterfailover']
350 a8083063 Iustin Pop
    AssertEqual(StartSSH(failovermaster['primary'],
351 a8083063 Iustin Pop
                         utils.ShellQuoteArgs(cmd)).wait(), 0)
352 a8083063 Iustin Pop
353 a8083063 Iustin Pop
    cmd = ['gnt-cluster', 'masterfailover']
354 a8083063 Iustin Pop
    AssertEqual(StartSSH(master['primary'],
355 a8083063 Iustin Pop
                         utils.ShellQuoteArgs(cmd)).wait(), 0)
356 a8083063 Iustin Pop
  finally:
357 a8083063 Iustin Pop
    ReleaseNode(failovermaster)
358 a8083063 Iustin Pop
359 a8083063 Iustin Pop
360 a8083063 Iustin Pop
def TestClusterDestroy():
361 a8083063 Iustin Pop
  """gnt-cluster destroy"""
362 a8083063 Iustin Pop
  cmd = ['gnt-cluster', 'destroy', '--yes-do-it']
363 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
364 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
365 a8083063 Iustin Pop
# }}}
366 a8083063 Iustin Pop
367 a8083063 Iustin Pop
# {{{ Node tests
368 a8083063 Iustin Pop
def _NodeAdd(node):
369 a8083063 Iustin Pop
  if node.get('_added', False):
370 a8083063 Iustin Pop
    raise Error, ("Node %s already in cluster" % node['primary'])
371 a8083063 Iustin Pop
372 a8083063 Iustin Pop
  cmd = ['gnt-node', 'add']
373 a8083063 Iustin Pop
  if node.get('secondary', None):
374 a8083063 Iustin Pop
    cmd.append('--secondary-ip=%s' % node['secondary'])
375 a8083063 Iustin Pop
  cmd.append(node['primary'])
376 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
377 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
378 a8083063 Iustin Pop
379 a8083063 Iustin Pop
  node['_added'] = True
380 a8083063 Iustin Pop
381 a8083063 Iustin Pop
382 a8083063 Iustin Pop
def TestNodeAddAll():
383 a8083063 Iustin Pop
  """Adding all nodes to cluster."""
384 a8083063 Iustin Pop
  master = GetMasterNode()
385 a8083063 Iustin Pop
  for node in cfg['nodes']:
386 a8083063 Iustin Pop
    if node != master:
387 a8083063 Iustin Pop
      _NodeAdd(node)
388 a8083063 Iustin Pop
389 a8083063 Iustin Pop
390 a8083063 Iustin Pop
def _NodeRemove(node):
391 a8083063 Iustin Pop
  cmd = ['gnt-node', 'remove', node['primary']]
392 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
393 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
394 a8083063 Iustin Pop
  node['_added'] = False
395 a8083063 Iustin Pop
396 a8083063 Iustin Pop
397 a8083063 Iustin Pop
def TestNodeRemoveAll():
398 a8083063 Iustin Pop
  """Removing all nodes from cluster."""
399 a8083063 Iustin Pop
  master = GetMasterNode()
400 a8083063 Iustin Pop
  for node in cfg['nodes']:
401 a8083063 Iustin Pop
    if node != master:
402 a8083063 Iustin Pop
      _NodeRemove(node)
403 a8083063 Iustin Pop
# }}}
404 a8083063 Iustin Pop
405 a8083063 Iustin Pop
# {{{ Instance tests
406 a8083063 Iustin Pop
def _DiskTest(node, instance, args):
407 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'add',
408 a8083063 Iustin Pop
         '--os-type=%s' % cfg['os'],
409 a8083063 Iustin Pop
         '--os-size=%s' % cfg['os-size'],
410 a8083063 Iustin Pop
         '--swap-size=%s' % cfg['swap-size'],
411 a8083063 Iustin Pop
         '--memory=%s' % cfg['mem'],
412 a8083063 Iustin Pop
         '--node=%s' % node['primary']]
413 a8083063 Iustin Pop
  if args:
414 a8083063 Iustin Pop
    cmd += args
415 a8083063 Iustin Pop
  cmd.append(instance['name'])
416 a8083063 Iustin Pop
417 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
418 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
419 a8083063 Iustin Pop
  return instance
420 a8083063 Iustin Pop
421 a8083063 Iustin Pop
422 a8083063 Iustin Pop
def TestInstanceAddWithPlainDisk(node):
423 a8083063 Iustin Pop
  """gnt-instance add -t plain"""
424 a8083063 Iustin Pop
  return _DiskTest(node, AcquireInstance(), ['--disk-template=plain'])
425 a8083063 Iustin Pop
426 a8083063 Iustin Pop
427 a8083063 Iustin Pop
def TestInstanceAddWithLocalMirrorDisk(node):
428 a8083063 Iustin Pop
  """gnt-instance add -t local_raid1"""
429 a8083063 Iustin Pop
  return _DiskTest(node, AcquireInstance(), ['--disk-template=local_raid1'])
430 a8083063 Iustin Pop
431 a8083063 Iustin Pop
432 a8083063 Iustin Pop
def TestInstanceAddWithRemoteRaidDisk(node, node2):
433 a8083063 Iustin Pop
  """gnt-instance add -t remote_raid1"""
434 a8083063 Iustin Pop
  return _DiskTest(node, AcquireInstance(),
435 a8083063 Iustin Pop
                   ['--disk-template=remote_raid1',
436 a8083063 Iustin Pop
                    '--secondary-node=%s' % node2['primary']])
437 a8083063 Iustin Pop
438 a8083063 Iustin Pop
439 a8083063 Iustin Pop
def TestInstanceRemove(instance):
440 a8083063 Iustin Pop
  """gnt-instance remove"""
441 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'remove', '-f', instance['name']]
442 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
443 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
444 a8083063 Iustin Pop
445 a8083063 Iustin Pop
  ReleaseInstance(instance)
446 a8083063 Iustin Pop
447 a8083063 Iustin Pop
448 a8083063 Iustin Pop
def TestInstanceStartup(instance):
449 a8083063 Iustin Pop
  """gnt-instance startup"""
450 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'startup', instance['name']]
451 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
452 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
453 a8083063 Iustin Pop
454 a8083063 Iustin Pop
455 a8083063 Iustin Pop
def TestInstanceShutdown(instance):
456 a8083063 Iustin Pop
  """gnt-instance shutdown"""
457 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'shutdown', instance['name']]
458 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
459 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
460 a8083063 Iustin Pop
461 a8083063 Iustin Pop
462 a8083063 Iustin Pop
def TestInstanceFailover(instance):
463 a8083063 Iustin Pop
  """gnt-instance failover"""
464 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'failover', '--force', instance['name']]
465 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
466 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
467 a8083063 Iustin Pop
# }}}
468 a8083063 Iustin Pop
469 a8083063 Iustin Pop
# {{{ Daemon tests
470 a8083063 Iustin Pop
def _ResolveInstanceName(instance):
471 a8083063 Iustin Pop
  """Gets the full Xen name of an instance.
472 a8083063 Iustin Pop

473 a8083063 Iustin Pop
  """
474 a8083063 Iustin Pop
  master = GetMasterNode()
475 a8083063 Iustin Pop
476 a8083063 Iustin Pop
  info_cmd = utils.ShellQuoteArgs(['gnt-instance', 'info', instance['name']])
477 a8083063 Iustin Pop
  sed_cmd = utils.ShellQuoteArgs(['sed', '-n', '-e', 's/^Instance name: *//p'])
478 a8083063 Iustin Pop
479 a8083063 Iustin Pop
  cmd = '%s | %s' % (info_cmd, sed_cmd)
480 a8083063 Iustin Pop
  p = subprocess.Popen(GetSSHCommand(master['primary'], cmd), shell=False,
481 a8083063 Iustin Pop
                       stdout=subprocess.PIPE)
482 a8083063 Iustin Pop
  AssertEqual(p.wait(), 0)
483 a8083063 Iustin Pop
484 a8083063 Iustin Pop
  return p.stdout.read().strip()
485 a8083063 Iustin Pop
486 a8083063 Iustin Pop
487 a8083063 Iustin Pop
def _InstanceRunning(node, name):
488 a8083063 Iustin Pop
  """Checks whether an instance is running.
489 a8083063 Iustin Pop

490 a8083063 Iustin Pop
  Args:
491 a8083063 Iustin Pop
    node: Node the instance runs on
492 a8083063 Iustin Pop
    name: Full name of Xen instance
493 a8083063 Iustin Pop
  """
494 a8083063 Iustin Pop
  cmd = utils.ShellQuoteArgs(['xm', 'list', name]) + ' >/dev/null'
495 a8083063 Iustin Pop
  ret = StartSSH(node['primary'], cmd).wait()
496 a8083063 Iustin Pop
  return ret == 0
497 a8083063 Iustin Pop
498 a8083063 Iustin Pop
499 a8083063 Iustin Pop
def _XmShutdownInstance(node, name):
500 a8083063 Iustin Pop
  """Shuts down instance using "xm" and waits for completion.
501 a8083063 Iustin Pop

502 a8083063 Iustin Pop
  Args:
503 a8083063 Iustin Pop
    node: Node the instance runs on
504 a8083063 Iustin Pop
    name: Full name of Xen instance
505 a8083063 Iustin Pop
  """
506 a8083063 Iustin Pop
  cmd = ['xm', 'shutdown', name]
507 a8083063 Iustin Pop
  AssertEqual(StartSSH(GetMasterNode()['primary'],
508 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
509 a8083063 Iustin Pop
510 a8083063 Iustin Pop
  # Wait up to a minute
511 a8083063 Iustin Pop
  end = time.time() + 60
512 a8083063 Iustin Pop
  while time.time() <= end:
513 a8083063 Iustin Pop
    if not _InstanceRunning(node, name):
514 a8083063 Iustin Pop
      break
515 a8083063 Iustin Pop
    time.sleep(5)
516 a8083063 Iustin Pop
  else:
517 a8083063 Iustin Pop
    raise Error, ("xm shutdown failed")
518 a8083063 Iustin Pop
519 a8083063 Iustin Pop
520 a8083063 Iustin Pop
def _ResetWatcherDaemon(node):
521 a8083063 Iustin Pop
  """Removes the watcher daemon's state file.
522 a8083063 Iustin Pop

523 a8083063 Iustin Pop
  Args:
524 a8083063 Iustin Pop
    node: Node to be reset
525 a8083063 Iustin Pop
  """
526 a8083063 Iustin Pop
  cmd = ['rm', '-f', constants.WATCHER_STATEFILE]
527 a8083063 Iustin Pop
  AssertEqual(StartSSH(node['primary'],
528 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
529 a8083063 Iustin Pop
530 a8083063 Iustin Pop
531 a8083063 Iustin Pop
def TestInstanceAutomaticRestart(node, instance):
532 a8083063 Iustin Pop
  """Test automatic restart of instance by ganeti-watcher.
533 a8083063 Iustin Pop

534 a8083063 Iustin Pop
  Note: takes up to 6 minutes to complete.
535 a8083063 Iustin Pop
  """
536 a8083063 Iustin Pop
  master = GetMasterNode()
537 a8083063 Iustin Pop
  inst_name = _ResolveInstanceName(instance)
538 a8083063 Iustin Pop
539 a8083063 Iustin Pop
  _ResetWatcherDaemon(node)
540 a8083063 Iustin Pop
  _XmShutdownInstance(node, inst_name)
541 a8083063 Iustin Pop
542 a8083063 Iustin Pop
  # Give it a bit more than five minutes to start again
543 a8083063 Iustin Pop
  restart_at = time.time() + 330
544 a8083063 Iustin Pop
545 a8083063 Iustin Pop
  # Wait until it's running again
546 a8083063 Iustin Pop
  while time.time() <= restart_at:
547 a8083063 Iustin Pop
    if _InstanceRunning(node, inst_name):
548 a8083063 Iustin Pop
      break
549 a8083063 Iustin Pop
    time.sleep(15)
550 a8083063 Iustin Pop
  else:
551 a8083063 Iustin Pop
    raise Error, ("Daemon didn't restart instance in time")
552 a8083063 Iustin Pop
553 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'info', inst_name]
554 a8083063 Iustin Pop
  AssertEqual(StartSSH(master['primary'],
555 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
556 a8083063 Iustin Pop
557 a8083063 Iustin Pop
558 a8083063 Iustin Pop
def TestInstanceConsecutiveFailures(node, instance):
559 a8083063 Iustin Pop
  """Test five consecutive instance failures.
560 a8083063 Iustin Pop

561 a8083063 Iustin Pop
  Note: takes at least 35 minutes to complete.
562 a8083063 Iustin Pop
  """
563 a8083063 Iustin Pop
  master = GetMasterNode()
564 a8083063 Iustin Pop
  inst_name = _ResolveInstanceName(instance)
565 a8083063 Iustin Pop
566 a8083063 Iustin Pop
  _ResetWatcherDaemon(node)
567 a8083063 Iustin Pop
  _XmShutdownInstance(node, inst_name)
568 a8083063 Iustin Pop
569 a8083063 Iustin Pop
  # Do shutdowns for 30 minutes
570 a8083063 Iustin Pop
  finished_at = time.time() + (35 * 60)
571 a8083063 Iustin Pop
572 a8083063 Iustin Pop
  while time.time() <= finished_at:
573 a8083063 Iustin Pop
    if _InstanceRunning(node, inst_name):
574 a8083063 Iustin Pop
      _XmShutdownInstance(node, inst_name)
575 a8083063 Iustin Pop
    time.sleep(30)
576 a8083063 Iustin Pop
577 a8083063 Iustin Pop
  # Check for some time whether the instance doesn't start again
578 a8083063 Iustin Pop
  check_until = time.time() + 330
579 a8083063 Iustin Pop
  while time.time() <= check_until:
580 a8083063 Iustin Pop
    if _InstanceRunning(node, inst_name):
581 a8083063 Iustin Pop
      raise Error, ("Instance started when it shouldn't")
582 a8083063 Iustin Pop
    time.sleep(30)
583 a8083063 Iustin Pop
584 a8083063 Iustin Pop
  cmd = ['gnt-instance', 'info', inst_name]
585 a8083063 Iustin Pop
  AssertEqual(StartSSH(master['primary'],
586 a8083063 Iustin Pop
                       utils.ShellQuoteArgs(cmd)).wait(), 0)
587 a8083063 Iustin Pop
# }}}
588 a8083063 Iustin Pop
589 a8083063 Iustin Pop
# {{{ Main program
590 a8083063 Iustin Pop
if __name__ == '__main__':
591 a8083063 Iustin Pop
  # {{{ Option parsing
592 a8083063 Iustin Pop
  parser = OptionParser(usage="%prog [options] <configfile>")
593 a8083063 Iustin Pop
  parser.add_option('--cleanup', dest='cleanup',
594 a8083063 Iustin Pop
      action="store_true",
595 a8083063 Iustin Pop
      help="Clean up cluster after testing?")
596 a8083063 Iustin Pop
  parser.add_option('--dry-run', dest='dry_run',
597 a8083063 Iustin Pop
      action="store_true",
598 a8083063 Iustin Pop
      help="Show what would be done")
599 a8083063 Iustin Pop
  parser.add_option('--verbose', dest='verbose',
600 a8083063 Iustin Pop
      action="store_true",
601 a8083063 Iustin Pop
      help="Verbose output")
602 a8083063 Iustin Pop
  parser.add_option('--yes-do-it', dest='yes_do_it',
603 a8083063 Iustin Pop
      action="store_true",
604 a8083063 Iustin Pop
      help="Really execute the tests")
605 a8083063 Iustin Pop
  (options, args) = parser.parse_args()
606 a8083063 Iustin Pop
  # }}}
607 a8083063 Iustin Pop
608 a8083063 Iustin Pop
  if len(args) == 1:
609 a8083063 Iustin Pop
    config_file = args[0]
610 a8083063 Iustin Pop
  else:
611 a8083063 Iustin Pop
    raise SyntaxError, ("Exactly one configuration file is expected")
612 a8083063 Iustin Pop
613 a8083063 Iustin Pop
  if not options.yes_do_it:
614 a8083063 Iustin Pop
    print ("Executing this script irreversibly destroys any Ganeti\n"
615 a8083063 Iustin Pop
           "configuration on all nodes involved. If you really want\n"
616 a8083063 Iustin Pop
           "to start testing, supply the --yes-do-it option.")
617 a8083063 Iustin Pop
    sys.exit(1)
618 a8083063 Iustin Pop
619 a8083063 Iustin Pop
  f = open(config_file, 'r')
620 a8083063 Iustin Pop
  try:
621 a8083063 Iustin Pop
    cfg = yaml.load(f.read())
622 a8083063 Iustin Pop
  finally:
623 a8083063 Iustin Pop
    f.close()
624 a8083063 Iustin Pop
625 a8083063 Iustin Pop
  RunTest(TestConfig)
626 a8083063 Iustin Pop
627 a8083063 Iustin Pop
  if TestEnabled('env'):
628 a8083063 Iustin Pop
    RunTest(TestSshConnection)
629 a8083063 Iustin Pop
    RunTest(TestIcmpPing)
630 a8083063 Iustin Pop
    RunTest(TestGanetiCommands)
631 a8083063 Iustin Pop
632 a8083063 Iustin Pop
  RunTest(TestClusterInit)
633 a8083063 Iustin Pop
634 a8083063 Iustin Pop
  if TestEnabled('cluster-verify'):
635 a8083063 Iustin Pop
    RunTest(TestClusterVerify)
636 a8083063 Iustin Pop
    RunTest(TestClusterInfo)
637 a8083063 Iustin Pop
638 a8083063 Iustin Pop
  RunTest(TestNodeAddAll)
639 a8083063 Iustin Pop
640 a8083063 Iustin Pop
  if TestEnabled('cluster-burnin'):
641 a8083063 Iustin Pop
    RunTest(TestClusterBurnin)
642 a8083063 Iustin Pop
643 a8083063 Iustin Pop
  if TestEnabled('cluster-master-failover'):
644 a8083063 Iustin Pop
    RunTest(TestClusterMasterFailover)
645 a8083063 Iustin Pop
646 a8083063 Iustin Pop
  node = AcquireNode()
647 a8083063 Iustin Pop
  try:
648 a8083063 Iustin Pop
    if TestEnabled('instance-add-plain-disk'):
649 a8083063 Iustin Pop
      instance = RunTest(TestInstanceAddWithPlainDisk, node)
650 a8083063 Iustin Pop
      RunTest(TestInstanceShutdown, instance)
651 a8083063 Iustin Pop
      RunTest(TestInstanceStartup, instance)
652 a8083063 Iustin Pop
653 a8083063 Iustin Pop
      if TestEnabled('instance-automatic-restart'):
654 a8083063 Iustin Pop
        RunTest(TestInstanceAutomaticRestart, node, instance)
655 a8083063 Iustin Pop
656 a8083063 Iustin Pop
      if TestEnabled('instance-consecutive-failures'):
657 a8083063 Iustin Pop
        RunTest(TestInstanceConsecutiveFailures, node, instance)
658 a8083063 Iustin Pop
659 a8083063 Iustin Pop
      RunTest(TestInstanceRemove, instance)
660 a8083063 Iustin Pop
      del instance
661 a8083063 Iustin Pop
662 a8083063 Iustin Pop
    if TestEnabled('instance-add-local-mirror-disk'):
663 a8083063 Iustin Pop
      instance = RunTest(TestInstanceAddWithLocalMirrorDisk, node)
664 a8083063 Iustin Pop
      RunTest(TestInstanceShutdown, instance)
665 a8083063 Iustin Pop
      RunTest(TestInstanceStartup, instance)
666 a8083063 Iustin Pop
      RunTest(TestInstanceRemove, instance)
667 a8083063 Iustin Pop
      del instance
668 a8083063 Iustin Pop
669 a8083063 Iustin Pop
    if TestEnabled('instance-add-remote-raid-disk'):
670 a8083063 Iustin Pop
      node2 = AcquireNode(exclude=node)
671 a8083063 Iustin Pop
      try:
672 a8083063 Iustin Pop
        instance = RunTest(TestInstanceAddWithRemoteRaidDisk, node, node2)
673 a8083063 Iustin Pop
        RunTest(TestInstanceShutdown, instance)
674 a8083063 Iustin Pop
        RunTest(TestInstanceStartup, instance)
675 a8083063 Iustin Pop
676 a8083063 Iustin Pop
        if TestEnabled('instance-failover'):
677 a8083063 Iustin Pop
          RunTest(TestInstanceFailover, instance)
678 a8083063 Iustin Pop
679 a8083063 Iustin Pop
        RunTest(TestInstanceRemove, instance)
680 a8083063 Iustin Pop
        del instance
681 a8083063 Iustin Pop
      finally:
682 a8083063 Iustin Pop
        ReleaseNode(node2)
683 a8083063 Iustin Pop
684 a8083063 Iustin Pop
  finally:
685 a8083063 Iustin Pop
    ReleaseNode(node)
686 a8083063 Iustin Pop
687 a8083063 Iustin Pop
  RunTest(TestNodeRemoveAll)
688 a8083063 Iustin Pop
689 a8083063 Iustin Pop
  if TestEnabled('cluster-destroy'):
690 a8083063 Iustin Pop
    RunTest(TestClusterDestroy)
691 a8083063 Iustin Pop
# }}}
692 a8083063 Iustin Pop
693 a8083063 Iustin Pop
# vim: foldmethod=marker :