Statistics
| Branch: | Tag: | Revision:

root / qa / qa_utils.py @ 1490a90c

History | View | Annotate | Download (25.1 kB)

1 c68d1f43 Michael Hanselmann
#
2 c68d1f43 Michael Hanselmann
#
3 c68d1f43 Michael Hanselmann
4 587f8ff6 Bernardo Dal Seno
# Copyright (C) 2007, 2011, 2012, 2013 Google Inc.
5 cec9845c Michael Hanselmann
#
6 cec9845c Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 cec9845c Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 cec9845c Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 cec9845c Michael Hanselmann
# (at your option) any later version.
10 cec9845c Michael Hanselmann
#
11 cec9845c Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 cec9845c Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 cec9845c Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 cec9845c Michael Hanselmann
# General Public License for more details.
15 cec9845c Michael Hanselmann
#
16 cec9845c Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 cec9845c Michael Hanselmann
# along with this program; if not, write to the Free Software
18 cec9845c Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 cec9845c Michael Hanselmann
# 02110-1301, USA.
20 cec9845c Michael Hanselmann
21 cec9845c Michael Hanselmann
22 cec9845c Michael Hanselmann
"""Utilities for QA tests.
23 cec9845c Michael Hanselmann

24 cec9845c Michael Hanselmann
"""
25 cec9845c Michael Hanselmann
26 ec996117 Bernardo Dal Seno
import copy
27 0e79564a Bernardo Dal Seno
import operator
28 cec9845c Michael Hanselmann
import os
29 0e79564a Bernardo Dal Seno
import random
30 e6ce18ac René Nussbaumer
import re
31 cec9845c Michael Hanselmann
import subprocess
32 0e79564a Bernardo Dal Seno
import sys
33 f7e6f3c8 Iustin Pop
import tempfile
34 0e79564a Bernardo Dal Seno
import yaml
35 cec9845c Michael Hanselmann
36 c9e05005 Michael Hanselmann
try:
37 c9e05005 Michael Hanselmann
  import functools
38 c9e05005 Michael Hanselmann
except ImportError, err:
39 c9e05005 Michael Hanselmann
  raise ImportError("Python 2.5 or higher is required: %s" % err)
40 c9e05005 Michael Hanselmann
41 cec9845c Michael Hanselmann
from ganeti import utils
42 288d6440 Michael Hanselmann
from ganeti import compat
43 2214cf14 Michael Hanselmann
from ganeti import constants
44 c9e05005 Michael Hanselmann
from ganeti import ht
45 48967eb0 Michael Hanselmann
from ganeti import pathutils
46 50eaa5da Michael Hanselmann
from ganeti import vcluster
47 cec9845c Michael Hanselmann
48 d5a9b556 Petr Pudlak
import colors
49 cec9845c Michael Hanselmann
import qa_config
50 cec9845c Michael Hanselmann
import qa_error
51 cec9845c Michael Hanselmann
52 cec9845c Michael Hanselmann
53 23269c5b Michael Hanselmann
_INFO_SEQ = None
54 23269c5b Michael Hanselmann
_WARNING_SEQ = None
55 23269c5b Michael Hanselmann
_ERROR_SEQ = None
56 23269c5b Michael Hanselmann
_RESET_SEQ = None
57 23269c5b Michael Hanselmann
58 f7e6f3c8 Iustin Pop
_MULTIPLEXERS = {}
59 f7e6f3c8 Iustin Pop
60 c9e05005 Michael Hanselmann
#: Unique ID per QA run
61 c9e05005 Michael Hanselmann
_RUN_UUID = utils.NewUUID()
62 c9e05005 Michael Hanselmann
63 afd5ca04 Iustin Pop
#: Path to the QA query output log file
64 afd5ca04 Iustin Pop
_QA_OUTPUT = pathutils.GetLogFilename("qa-output")
65 afd5ca04 Iustin Pop
66 c9e05005 Michael Hanselmann
67 c9e05005 Michael Hanselmann
(INST_DOWN,
68 c9e05005 Michael Hanselmann
 INST_UP) = range(500, 502)
69 c9e05005 Michael Hanselmann
70 c9e05005 Michael Hanselmann
(FIRST_ARG,
71 c9e05005 Michael Hanselmann
 RETURN_VALUE) = range(1000, 1002)
72 c9e05005 Michael Hanselmann
73 23269c5b Michael Hanselmann
74 23269c5b Michael Hanselmann
def _SetupColours():
75 23269c5b Michael Hanselmann
  """Initializes the colour constants.
76 23269c5b Michael Hanselmann

77 23269c5b Michael Hanselmann
  """
78 b459a848 Andrea Spadaccini
  # pylint: disable=W0603
79 3582eef6 Iustin Pop
  # due to global usage
80 23269c5b Michael Hanselmann
  global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
81 23269c5b Michael Hanselmann
82 dfe11bad Michael Hanselmann
  # Don't use colours if stdout isn't a terminal
83 dfe11bad Michael Hanselmann
  if not sys.stdout.isatty():
84 dfe11bad Michael Hanselmann
    return
85 dfe11bad Michael Hanselmann
86 23269c5b Michael Hanselmann
  try:
87 23269c5b Michael Hanselmann
    import curses
88 23269c5b Michael Hanselmann
  except ImportError:
89 23269c5b Michael Hanselmann
    # Don't use colours if curses module can't be imported
90 23269c5b Michael Hanselmann
    return
91 23269c5b Michael Hanselmann
92 23269c5b Michael Hanselmann
  curses.setupterm()
93 23269c5b Michael Hanselmann
94 23269c5b Michael Hanselmann
  _RESET_SEQ = curses.tigetstr("op")
95 23269c5b Michael Hanselmann
96 23269c5b Michael Hanselmann
  setaf = curses.tigetstr("setaf")
97 23269c5b Michael Hanselmann
  _INFO_SEQ = curses.tparm(setaf, curses.COLOR_GREEN)
98 23269c5b Michael Hanselmann
  _WARNING_SEQ = curses.tparm(setaf, curses.COLOR_YELLOW)
99 23269c5b Michael Hanselmann
  _ERROR_SEQ = curses.tparm(setaf, curses.COLOR_RED)
100 23269c5b Michael Hanselmann
101 23269c5b Michael Hanselmann
102 23269c5b Michael Hanselmann
_SetupColours()
103 23269c5b Michael Hanselmann
104 23269c5b Michael Hanselmann
105 eaef8a05 Michael Hanselmann
def AssertIn(item, sequence):
106 eaef8a05 Michael Hanselmann
  """Raises an error when item is not in sequence.
107 eaef8a05 Michael Hanselmann

108 eaef8a05 Michael Hanselmann
  """
109 eaef8a05 Michael Hanselmann
  if item not in sequence:
110 d0c8c01d Iustin Pop
    raise qa_error.Error("%r not in %r" % (item, sequence))
111 eaef8a05 Michael Hanselmann
112 eaef8a05 Michael Hanselmann
113 79eac09b Michael Hanselmann
def AssertNotIn(item, sequence):
114 79eac09b Michael Hanselmann
  """Raises an error when item is in sequence.
115 79eac09b Michael Hanselmann

116 79eac09b Michael Hanselmann
  """
117 79eac09b Michael Hanselmann
  if item in sequence:
118 d0c8c01d Iustin Pop
    raise qa_error.Error("%r in %r" % (item, sequence))
119 79eac09b Michael Hanselmann
120 79eac09b Michael Hanselmann
121 e8ae0c20 Michael Hanselmann
def AssertEqual(first, second):
122 cec9845c Michael Hanselmann
  """Raises an error when values aren't equal.
123 cec9845c Michael Hanselmann

124 cec9845c Michael Hanselmann
  """
125 cec9845c Michael Hanselmann
  if not first == second:
126 d0c8c01d Iustin Pop
    raise qa_error.Error("%r == %r" % (first, second))
127 e8ae0c20 Michael Hanselmann
128 e8ae0c20 Michael Hanselmann
129 e6ce18ac René Nussbaumer
def AssertMatch(string, pattern):
130 e6ce18ac René Nussbaumer
  """Raises an error when string doesn't match regexp pattern.
131 e6ce18ac René Nussbaumer

132 e6ce18ac René Nussbaumer
  """
133 e6ce18ac René Nussbaumer
  if not re.match(pattern, string):
134 e6ce18ac René Nussbaumer
    raise qa_error.Error("%r doesn't match /%r/" % (string, pattern))
135 e6ce18ac René Nussbaumer
136 e6ce18ac René Nussbaumer
137 6998aefe Michael Hanselmann
def _GetName(entity, fn):
138 889bed16 Michael Hanselmann
  """Tries to get name of an entity.
139 889bed16 Michael Hanselmann

140 889bed16 Michael Hanselmann
  @type entity: string or dict
141 6998aefe Michael Hanselmann
  @param fn: Function retrieving name from entity
142 889bed16 Michael Hanselmann

143 889bed16 Michael Hanselmann
  """
144 889bed16 Michael Hanselmann
  if isinstance(entity, basestring):
145 889bed16 Michael Hanselmann
    result = entity
146 889bed16 Michael Hanselmann
  else:
147 6998aefe Michael Hanselmann
    result = fn(entity)
148 889bed16 Michael Hanselmann
149 889bed16 Michael Hanselmann
  if not ht.TNonEmptyString(result):
150 889bed16 Michael Hanselmann
    raise Exception("Invalid name '%s'" % result)
151 889bed16 Michael Hanselmann
152 889bed16 Michael Hanselmann
  return result
153 889bed16 Michael Hanselmann
154 889bed16 Michael Hanselmann
155 587f8ff6 Bernardo Dal Seno
def _AssertRetCode(rcode, fail, cmdstr, nodename):
156 587f8ff6 Bernardo Dal Seno
  """Check the return value from a command and possibly raise an exception.
157 587f8ff6 Bernardo Dal Seno

158 587f8ff6 Bernardo Dal Seno
  """
159 587f8ff6 Bernardo Dal Seno
  if fail and rcode == 0:
160 587f8ff6 Bernardo Dal Seno
    raise qa_error.Error("Command '%s' on node %s was expected to fail but"
161 587f8ff6 Bernardo Dal Seno
                         " didn't" % (cmdstr, nodename))
162 587f8ff6 Bernardo Dal Seno
  elif not fail and rcode != 0:
163 587f8ff6 Bernardo Dal Seno
    raise qa_error.Error("Command '%s' on node %s failed, exit code %s" %
164 587f8ff6 Bernardo Dal Seno
                         (cmdstr, nodename, rcode))
165 587f8ff6 Bernardo Dal Seno
166 587f8ff6 Bernardo Dal Seno
167 56b9f2db Iustin Pop
def AssertCommand(cmd, fail=False, node=None, log_cmd=True):
168 2f4b4f78 Iustin Pop
  """Checks that a remote command succeeds.
169 2f4b4f78 Iustin Pop

170 2f4b4f78 Iustin Pop
  @param cmd: either a string (the command to execute) or a list (to
171 2f4b4f78 Iustin Pop
      be converted using L{utils.ShellQuoteArgs} into a string)
172 2f4b4f78 Iustin Pop
  @type fail: boolean
173 2f4b4f78 Iustin Pop
  @param fail: if the command is expected to fail instead of succeeding
174 2f4b4f78 Iustin Pop
  @param node: if passed, it should be the node on which the command
175 2f4b4f78 Iustin Pop
      should be executed, instead of the master node (can be either a
176 2f4b4f78 Iustin Pop
      dict or a string)
177 56b9f2db Iustin Pop
  @param log_cmd: if False, the command won't be logged (simply passed to
178 56b9f2db Iustin Pop
      StartSSH)
179 05325a35 Bernardo Dal Seno
  @return: the return code of the command
180 05325a35 Bernardo Dal Seno
  @raise qa_error.Error: if the command fails when it shouldn't or vice versa
181 2f4b4f78 Iustin Pop

182 2f4b4f78 Iustin Pop
  """
183 2f4b4f78 Iustin Pop
  if node is None:
184 2f4b4f78 Iustin Pop
    node = qa_config.GetMasterNode()
185 2f4b4f78 Iustin Pop
186 6998aefe Michael Hanselmann
  nodename = _GetName(node, operator.attrgetter("primary"))
187 2f4b4f78 Iustin Pop
188 2f4b4f78 Iustin Pop
  if isinstance(cmd, basestring):
189 2f4b4f78 Iustin Pop
    cmdstr = cmd
190 2f4b4f78 Iustin Pop
  else:
191 2f4b4f78 Iustin Pop
    cmdstr = utils.ShellQuoteArgs(cmd)
192 2f4b4f78 Iustin Pop
193 56b9f2db Iustin Pop
  rcode = StartSSH(nodename, cmdstr, log_cmd=log_cmd).wait()
194 587f8ff6 Bernardo Dal Seno
  _AssertRetCode(rcode, fail, cmdstr, nodename)
195 2f4b4f78 Iustin Pop
196 2214cf14 Michael Hanselmann
  return rcode
197 2214cf14 Michael Hanselmann
198 2f4b4f78 Iustin Pop
199 afd5ca04 Iustin Pop
def AssertRedirectedCommand(cmd, fail=False, node=None, log_cmd=True):
200 afd5ca04 Iustin Pop
  """Executes a command with redirected output.
201 afd5ca04 Iustin Pop

202 afd5ca04 Iustin Pop
  The log will go to the qa-output log file in the ganeti log
203 afd5ca04 Iustin Pop
  directory on the node where the command is executed. The fail and
204 afd5ca04 Iustin Pop
  node parameters are passed unchanged to AssertCommand.
205 afd5ca04 Iustin Pop

206 afd5ca04 Iustin Pop
  @param cmd: the command to be executed, as a list; a string is not
207 afd5ca04 Iustin Pop
      supported
208 afd5ca04 Iustin Pop

209 afd5ca04 Iustin Pop
  """
210 afd5ca04 Iustin Pop
  if not isinstance(cmd, list):
211 afd5ca04 Iustin Pop
    raise qa_error.Error("Non-list passed to AssertRedirectedCommand")
212 afd5ca04 Iustin Pop
  ofile = utils.ShellQuote(_QA_OUTPUT)
213 afd5ca04 Iustin Pop
  cmdstr = utils.ShellQuoteArgs(cmd)
214 afd5ca04 Iustin Pop
  AssertCommand("echo ---- $(date) %s ---- >> %s" % (cmdstr, ofile),
215 afd5ca04 Iustin Pop
                fail=False, node=node, log_cmd=False)
216 afd5ca04 Iustin Pop
  return AssertCommand(cmdstr + " >> %s" % ofile,
217 afd5ca04 Iustin Pop
                       fail=fail, node=node, log_cmd=log_cmd)
218 afd5ca04 Iustin Pop
219 afd5ca04 Iustin Pop
220 f14a8b15 Iustin Pop
def GetSSHCommand(node, cmd, strict=True, opts=None, tty=None):
221 cec9845c Michael Hanselmann
  """Builds SSH command to be executed.
222 cec9845c Michael Hanselmann

223 0a05f959 Adeodato Simo
  @type node: string
224 0a05f959 Adeodato Simo
  @param node: node the command should run on
225 0a05f959 Adeodato Simo
  @type cmd: string
226 f7e6f3c8 Iustin Pop
  @param cmd: command to be executed in the node; if None or empty
227 f7e6f3c8 Iustin Pop
      string, no command will be executed
228 0a05f959 Adeodato Simo
  @type strict: boolean
229 0a05f959 Adeodato Simo
  @param strict: whether to enable strict host key checking
230 f7e6f3c8 Iustin Pop
  @type opts: list
231 f7e6f3c8 Iustin Pop
  @param opts: list of additional options
232 f14a8b15 Iustin Pop
  @type tty: boolean or None
233 f14a8b15 Iustin Pop
  @param tty: if we should use tty; if None, will be auto-detected
234 c68d1f43 Michael Hanselmann

235 cec9845c Michael Hanselmann
  """
236 710bc88c Iustin Pop
  args = ["ssh", "-oEscapeChar=none", "-oBatchMode=yes", "-lroot"]
237 50265802 René Nussbaumer
238 f14a8b15 Iustin Pop
  if tty is None:
239 f14a8b15 Iustin Pop
    tty = sys.stdout.isatty()
240 f14a8b15 Iustin Pop
241 50265802 René Nussbaumer
  if tty:
242 50265802 René Nussbaumer
    args.append("-t")
243 cec9845c Michael Hanselmann
244 cec9845c Michael Hanselmann
  if strict:
245 d0c8c01d Iustin Pop
    tmp = "yes"
246 cec9845c Michael Hanselmann
  else:
247 d0c8c01d Iustin Pop
    tmp = "no"
248 d0c8c01d Iustin Pop
  args.append("-oStrictHostKeyChecking=%s" % tmp)
249 d0c8c01d Iustin Pop
  args.append("-oClearAllForwardings=yes")
250 d0c8c01d Iustin Pop
  args.append("-oForwardAgent=yes")
251 f7e6f3c8 Iustin Pop
  if opts:
252 f7e6f3c8 Iustin Pop
    args.extend(opts)
253 f7e6f3c8 Iustin Pop
  if node in _MULTIPLEXERS:
254 f7e6f3c8 Iustin Pop
    spath = _MULTIPLEXERS[node][0]
255 d0c8c01d Iustin Pop
    args.append("-oControlPath=%s" % spath)
256 d0c8c01d Iustin Pop
    args.append("-oControlMaster=no")
257 50eaa5da Michael Hanselmann
258 50eaa5da Michael Hanselmann
  (vcluster_master, vcluster_basedir) = \
259 50eaa5da Michael Hanselmann
    qa_config.GetVclusterSettings()
260 50eaa5da Michael Hanselmann
261 50eaa5da Michael Hanselmann
  if vcluster_master:
262 50eaa5da Michael Hanselmann
    args.append(vcluster_master)
263 50eaa5da Michael Hanselmann
    args.append("%s/%s/cmd" % (vcluster_basedir, node))
264 50eaa5da Michael Hanselmann
265 50eaa5da Michael Hanselmann
    if cmd:
266 50eaa5da Michael Hanselmann
      # For virtual clusters the whole command must be wrapped using the "cmd"
267 50eaa5da Michael Hanselmann
      # script, as that script sets a number of environment variables. If the
268 50eaa5da Michael Hanselmann
      # command contains shell meta characters the whole command needs to be
269 50eaa5da Michael Hanselmann
      # quoted.
270 50eaa5da Michael Hanselmann
      args.append(utils.ShellQuote(cmd))
271 50eaa5da Michael Hanselmann
  else:
272 50eaa5da Michael Hanselmann
    args.append(node)
273 50eaa5da Michael Hanselmann
274 50eaa5da Michael Hanselmann
    if cmd:
275 50eaa5da Michael Hanselmann
      args.append(cmd)
276 cec9845c Michael Hanselmann
277 cec9845c Michael Hanselmann
  return args
278 cec9845c Michael Hanselmann
279 cec9845c Michael Hanselmann
280 56b9f2db Iustin Pop
def StartLocalCommand(cmd, _nolog_opts=False, log_cmd=True, **kwargs):
281 5d831182 Michael Hanselmann
  """Starts a local command.
282 5d831182 Michael Hanselmann

283 5d831182 Michael Hanselmann
  """
284 56b9f2db Iustin Pop
  if log_cmd:
285 56b9f2db Iustin Pop
    if _nolog_opts:
286 56b9f2db Iustin Pop
      pcmd = [i for i in cmd if not i.startswith("-")]
287 56b9f2db Iustin Pop
    else:
288 56b9f2db Iustin Pop
      pcmd = cmd
289 d5a9b556 Petr Pudlak
    print "%s %s" % (colors.colorize("Command:", colors.CYAN),
290 d5a9b556 Petr Pudlak
                     utils.ShellQuoteArgs(pcmd))
291 5d831182 Michael Hanselmann
  return subprocess.Popen(cmd, shell=False, **kwargs)
292 5d831182 Michael Hanselmann
293 5d831182 Michael Hanselmann
294 56b9f2db Iustin Pop
def StartSSH(node, cmd, strict=True, log_cmd=True):
295 cec9845c Michael Hanselmann
  """Starts SSH.
296 cec9845c Michael Hanselmann

297 cec9845c Michael Hanselmann
  """
298 710bc88c Iustin Pop
  return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict),
299 56b9f2db Iustin Pop
                           _nolog_opts=True, log_cmd=log_cmd)
300 4b62db14 Michael Hanselmann
301 4b62db14 Michael Hanselmann
302 f7e6f3c8 Iustin Pop
def StartMultiplexer(node):
303 f7e6f3c8 Iustin Pop
  """Starts a multiplexer command.
304 f7e6f3c8 Iustin Pop

305 f7e6f3c8 Iustin Pop
  @param node: the node for which to open the multiplexer
306 f7e6f3c8 Iustin Pop

307 f7e6f3c8 Iustin Pop
  """
308 f7e6f3c8 Iustin Pop
  if node in _MULTIPLEXERS:
309 f7e6f3c8 Iustin Pop
    return
310 f7e6f3c8 Iustin Pop
311 f7e6f3c8 Iustin Pop
  # Note: yes, we only need mktemp, since we'll remove the file anyway
312 f7e6f3c8 Iustin Pop
  sname = tempfile.mktemp(prefix="ganeti-qa-multiplexer.")
313 f7e6f3c8 Iustin Pop
  utils.RemoveFile(sname)
314 f7e6f3c8 Iustin Pop
  opts = ["-N", "-oControlPath=%s" % sname, "-oControlMaster=yes"]
315 f7e6f3c8 Iustin Pop
  print "Created socket at %s" % sname
316 f7e6f3c8 Iustin Pop
  child = StartLocalCommand(GetSSHCommand(node, None, opts=opts))
317 f7e6f3c8 Iustin Pop
  _MULTIPLEXERS[node] = (sname, child)
318 f7e6f3c8 Iustin Pop
319 f7e6f3c8 Iustin Pop
320 f7e6f3c8 Iustin Pop
def CloseMultiplexers():
321 f7e6f3c8 Iustin Pop
  """Closes all current multiplexers and cleans up.
322 f7e6f3c8 Iustin Pop

323 f7e6f3c8 Iustin Pop
  """
324 f7e6f3c8 Iustin Pop
  for node in _MULTIPLEXERS.keys():
325 f7e6f3c8 Iustin Pop
    (sname, child) = _MULTIPLEXERS.pop(node)
326 f7e6f3c8 Iustin Pop
    utils.KillProcess(child.pid, timeout=10, waitpid=True)
327 f7e6f3c8 Iustin Pop
    utils.RemoveFile(sname)
328 f7e6f3c8 Iustin Pop
329 f7e6f3c8 Iustin Pop
330 587f8ff6 Bernardo Dal Seno
def GetCommandOutput(node, cmd, tty=None, fail=False):
331 4b62db14 Michael Hanselmann
  """Returns the output of a command executed on the given node.
332 4b62db14 Michael Hanselmann

333 587f8ff6 Bernardo Dal Seno
  @type node: string
334 587f8ff6 Bernardo Dal Seno
  @param node: node the command should run on
335 587f8ff6 Bernardo Dal Seno
  @type cmd: string
336 587f8ff6 Bernardo Dal Seno
  @param cmd: command to be executed in the node (cannot be empty or None)
337 587f8ff6 Bernardo Dal Seno
  @type tty: bool or None
338 587f8ff6 Bernardo Dal Seno
  @param tty: if we should use tty; if None, it will be auto-detected
339 587f8ff6 Bernardo Dal Seno
  @type fail: bool
340 587f8ff6 Bernardo Dal Seno
  @param fail: whether the command is expected to fail
341 4b62db14 Michael Hanselmann
  """
342 587f8ff6 Bernardo Dal Seno
  assert cmd
343 50265802 René Nussbaumer
  p = StartLocalCommand(GetSSHCommand(node, cmd, tty=tty),
344 50265802 René Nussbaumer
                        stdout=subprocess.PIPE)
345 587f8ff6 Bernardo Dal Seno
  rcode = p.wait()
346 3b0db9e3 Michael Hanselmann
  _AssertRetCode(rcode, fail, cmd, node)
347 4b62db14 Michael Hanselmann
  return p.stdout.read()
348 cec9845c Michael Hanselmann
349 cec9845c Michael Hanselmann
350 0e79564a Bernardo Dal Seno
def GetObjectInfo(infocmd):
351 0e79564a Bernardo Dal Seno
  """Get and parse information about a Ganeti object.
352 0e79564a Bernardo Dal Seno

353 0e79564a Bernardo Dal Seno
  @type infocmd: list of strings
354 0e79564a Bernardo Dal Seno
  @param infocmd: command to be executed, e.g. ["gnt-cluster", "info"]
355 0e79564a Bernardo Dal Seno
  @return: the information parsed, appropriately stored in dictionaries,
356 0e79564a Bernardo Dal Seno
      lists...
357 0e79564a Bernardo Dal Seno

358 0e79564a Bernardo Dal Seno
  """
359 0e79564a Bernardo Dal Seno
  master = qa_config.GetMasterNode()
360 0e79564a Bernardo Dal Seno
  cmdline = utils.ShellQuoteArgs(infocmd)
361 0e79564a Bernardo Dal Seno
  info_out = GetCommandOutput(master.primary, cmdline)
362 0e79564a Bernardo Dal Seno
  return yaml.load(info_out)
363 0e79564a Bernardo Dal Seno
364 0e79564a Bernardo Dal Seno
365 cec9845c Michael Hanselmann
def UploadFile(node, src):
366 cec9845c Michael Hanselmann
  """Uploads a file to a node and returns the filename.
367 cec9845c Michael Hanselmann

368 cec9845c Michael Hanselmann
  Caller needs to remove the returned file on the node when it's not needed
369 cec9845c Michael Hanselmann
  anymore.
370 49d50e52 Michael Hanselmann

371 cec9845c Michael Hanselmann
  """
372 cec9845c Michael Hanselmann
  # Make sure nobody else has access to it while preserving local permissions
373 cec9845c Michael Hanselmann
  mode = os.stat(src).st_mode & 0700
374 cec9845c Michael Hanselmann
375 f90a2d0c Thomas Thrainer
  cmd = ('tmp=$(mktemp --tmpdir gnt.XXXXXX) && '
376 f90a2d0c Thomas Thrainer
         'chmod %o "${tmp}" && '
377 cec9845c Michael Hanselmann
         '[[ -f "${tmp}" ]] && '
378 cec9845c Michael Hanselmann
         'cat > "${tmp}" && '
379 cec9845c Michael Hanselmann
         'echo "${tmp}"') % mode
380 cec9845c Michael Hanselmann
381 d0c8c01d Iustin Pop
  f = open(src, "r")
382 cec9845c Michael Hanselmann
  try:
383 cec9845c Michael Hanselmann
    p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
384 cec9845c Michael Hanselmann
                         stdout=subprocess.PIPE)
385 cec9845c Michael Hanselmann
    AssertEqual(p.wait(), 0)
386 cec9845c Michael Hanselmann
387 cec9845c Michael Hanselmann
    # Return temporary filename
388 cec9845c Michael Hanselmann
    return p.stdout.read().strip()
389 cec9845c Michael Hanselmann
  finally:
390 cec9845c Michael Hanselmann
    f.close()
391 5d640672 Michael Hanselmann
392 5d640672 Michael Hanselmann
393 b9955569 René Nussbaumer
def UploadData(node, data, mode=0600, filename=None):
394 b9955569 René Nussbaumer
  """Uploads data to a node and returns the filename.
395 b9955569 René Nussbaumer

396 b9955569 René Nussbaumer
  Caller needs to remove the returned file on the node when it's not needed
397 b9955569 René Nussbaumer
  anymore.
398 b9955569 René Nussbaumer

399 b9955569 René Nussbaumer
  """
400 b9955569 René Nussbaumer
  if filename:
401 b9955569 René Nussbaumer
    tmp = "tmp=%s" % utils.ShellQuote(filename)
402 b9955569 René Nussbaumer
  else:
403 f90a2d0c Thomas Thrainer
    tmp = ('tmp=$(mktemp --tmpdir gnt.XXXXXX) && '
404 f90a2d0c Thomas Thrainer
           'chmod %o "${tmp}"') % mode
405 b9955569 René Nussbaumer
  cmd = ("%s && "
406 b9955569 René Nussbaumer
         "[[ -f \"${tmp}\" ]] && "
407 b9955569 René Nussbaumer
         "cat > \"${tmp}\" && "
408 b9955569 René Nussbaumer
         "echo \"${tmp}\"") % tmp
409 b9955569 René Nussbaumer
410 b9955569 René Nussbaumer
  p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False,
411 b9955569 René Nussbaumer
                       stdin=subprocess.PIPE, stdout=subprocess.PIPE)
412 b9955569 René Nussbaumer
  p.stdin.write(data)
413 b9955569 René Nussbaumer
  p.stdin.close()
414 b9955569 René Nussbaumer
  AssertEqual(p.wait(), 0)
415 b9955569 René Nussbaumer
416 b9955569 René Nussbaumer
  # Return temporary filename
417 b9955569 René Nussbaumer
  return p.stdout.read().strip()
418 b9955569 René Nussbaumer
419 b9955569 René Nussbaumer
420 49d50e52 Michael Hanselmann
def BackupFile(node, path):
421 49d50e52 Michael Hanselmann
  """Creates a backup of a file on the node and returns the filename.
422 49d50e52 Michael Hanselmann

423 49d50e52 Michael Hanselmann
  Caller needs to remove the returned file on the node when it's not needed
424 49d50e52 Michael Hanselmann
  anymore.
425 49d50e52 Michael Hanselmann

426 49d50e52 Michael Hanselmann
  """
427 7160f14a Michael Hanselmann
  vpath = MakeNodePath(node, path)
428 7160f14a Michael Hanselmann
429 f90a2d0c Thomas Thrainer
  cmd = ("tmp=$(mktemp .gnt.XXXXXX --tmpdir=$(dirname %s)) && "
430 49d50e52 Michael Hanselmann
         "[[ -f \"$tmp\" ]] && "
431 49d50e52 Michael Hanselmann
         "cp %s $tmp && "
432 7160f14a Michael Hanselmann
         "echo $tmp") % (utils.ShellQuote(vpath), utils.ShellQuote(vpath))
433 49d50e52 Michael Hanselmann
434 49d50e52 Michael Hanselmann
  # Return temporary filename
435 7160f14a Michael Hanselmann
  result = GetCommandOutput(node, cmd).strip()
436 7160f14a Michael Hanselmann
437 7160f14a Michael Hanselmann
  print "Backup filename: %s" % result
438 7160f14a Michael Hanselmann
439 7160f14a Michael Hanselmann
  return result
440 49d50e52 Michael Hanselmann
441 49d50e52 Michael Hanselmann
442 5d640672 Michael Hanselmann
def ResolveInstanceName(instance):
443 5d640672 Michael Hanselmann
  """Gets the full name of an instance.
444 5d640672 Michael Hanselmann

445 46f9a948 Michael Hanselmann
  @type instance: string
446 46f9a948 Michael Hanselmann
  @param instance: Instance name
447 46f9a948 Michael Hanselmann

448 5d640672 Michael Hanselmann
  """
449 2cbcf95d Bernardo Dal Seno
  info = GetObjectInfo(["gnt-instance", "info", instance])
450 2cbcf95d Bernardo Dal Seno
  return info[0]["Instance name"]
451 4b62db14 Michael Hanselmann
452 4b62db14 Michael Hanselmann
453 4b62db14 Michael Hanselmann
def ResolveNodeName(node):
454 4b62db14 Michael Hanselmann
  """Gets the full name of a node.
455 4b62db14 Michael Hanselmann

456 4b62db14 Michael Hanselmann
  """
457 5f6d1b42 Bernardo Dal Seno
  info = GetObjectInfo(["gnt-node", "info", node.primary])
458 5f6d1b42 Bernardo Dal Seno
  return info[0]["Node name"]
459 4b62db14 Michael Hanselmann
460 4b62db14 Michael Hanselmann
461 4b62db14 Michael Hanselmann
def GetNodeInstances(node, secondaries=False):
462 4b62db14 Michael Hanselmann
  """Gets a list of instances on a node.
463 4b62db14 Michael Hanselmann

464 4b62db14 Michael Hanselmann
  """
465 5d640672 Michael Hanselmann
  master = qa_config.GetMasterNode()
466 4b62db14 Michael Hanselmann
  node_name = ResolveNodeName(node)
467 5d640672 Michael Hanselmann
468 4b62db14 Michael Hanselmann
  # Get list of all instances
469 d0c8c01d Iustin Pop
  cmd = ["gnt-instance", "list", "--separator=:", "--no-headers",
470 d0c8c01d Iustin Pop
         "--output=name,pnode,snodes"]
471 aecba21e Michael Hanselmann
  output = GetCommandOutput(master.primary, utils.ShellQuoteArgs(cmd))
472 4b62db14 Michael Hanselmann
473 4b62db14 Michael Hanselmann
  instances = []
474 4b62db14 Michael Hanselmann
  for line in output.splitlines():
475 d0c8c01d Iustin Pop
    (name, pnode, snodes) = line.split(":", 2)
476 4b62db14 Michael Hanselmann
    if ((not secondaries and pnode == node_name) or
477 d0c8c01d Iustin Pop
        (secondaries and node_name in snodes.split(","))):
478 4b62db14 Michael Hanselmann
      instances.append(name)
479 5d640672 Michael Hanselmann
480 4b62db14 Michael Hanselmann
  return instances
481 23269c5b Michael Hanselmann
482 23269c5b Michael Hanselmann
483 288d6440 Michael Hanselmann
def _SelectQueryFields(rnd, fields):
484 288d6440 Michael Hanselmann
  """Generates a list of fields for query tests.
485 288d6440 Michael Hanselmann

486 288d6440 Michael Hanselmann
  """
487 288d6440 Michael Hanselmann
  # Create copy for shuffling
488 288d6440 Michael Hanselmann
  fields = list(fields)
489 288d6440 Michael Hanselmann
  rnd.shuffle(fields)
490 288d6440 Michael Hanselmann
491 288d6440 Michael Hanselmann
  # Check all fields
492 288d6440 Michael Hanselmann
  yield fields
493 288d6440 Michael Hanselmann
  yield sorted(fields)
494 288d6440 Michael Hanselmann
495 288d6440 Michael Hanselmann
  # Duplicate fields
496 288d6440 Michael Hanselmann
  yield fields + fields
497 288d6440 Michael Hanselmann
498 288d6440 Michael Hanselmann
  # Check small groups of fields
499 288d6440 Michael Hanselmann
  while fields:
500 288d6440 Michael Hanselmann
    yield [fields.pop() for _ in range(rnd.randint(2, 10)) if fields]
501 288d6440 Michael Hanselmann
502 288d6440 Michael Hanselmann
503 288d6440 Michael Hanselmann
def _List(listcmd, fields, names):
504 288d6440 Michael Hanselmann
  """Runs a list command.
505 288d6440 Michael Hanselmann

506 288d6440 Michael Hanselmann
  """
507 288d6440 Michael Hanselmann
  master = qa_config.GetMasterNode()
508 288d6440 Michael Hanselmann
509 58ea8d17 Michael Hanselmann
  cmd = [listcmd, "list", "--separator=|", "--no-headers",
510 288d6440 Michael Hanselmann
         "--output", ",".join(fields)]
511 288d6440 Michael Hanselmann
512 288d6440 Michael Hanselmann
  if names:
513 288d6440 Michael Hanselmann
    cmd.extend(names)
514 288d6440 Michael Hanselmann
515 aecba21e Michael Hanselmann
  return GetCommandOutput(master.primary,
516 288d6440 Michael Hanselmann
                          utils.ShellQuoteArgs(cmd)).splitlines()
517 288d6440 Michael Hanselmann
518 288d6440 Michael Hanselmann
519 d352b796 Helga Velroyen
def GenericQueryTest(cmd, fields, namefield="name", test_unknown=True):
520 288d6440 Michael Hanselmann
  """Runs a number of tests on query commands.
521 288d6440 Michael Hanselmann

522 288d6440 Michael Hanselmann
  @param cmd: Command name
523 288d6440 Michael Hanselmann
  @param fields: List of field names
524 288d6440 Michael Hanselmann

525 288d6440 Michael Hanselmann
  """
526 288d6440 Michael Hanselmann
  rnd = random.Random(hash(cmd))
527 288d6440 Michael Hanselmann
528 3582eef6 Iustin Pop
  fields = list(fields)
529 288d6440 Michael Hanselmann
  rnd.shuffle(fields)
530 288d6440 Michael Hanselmann
531 288d6440 Michael Hanselmann
  # Test a number of field combinations
532 288d6440 Michael Hanselmann
  for testfields in _SelectQueryFields(rnd, fields):
533 93146c8c Iustin Pop
    AssertRedirectedCommand([cmd, "list", "--output", ",".join(testfields)])
534 288d6440 Michael Hanselmann
535 0fdf247d Michael Hanselmann
  if namefield is not None:
536 0fdf247d Michael Hanselmann
    namelist_fn = compat.partial(_List, cmd, [namefield])
537 288d6440 Michael Hanselmann
538 0fdf247d Michael Hanselmann
    # When no names were requested, the list must be sorted
539 0fdf247d Michael Hanselmann
    names = namelist_fn(None)
540 0fdf247d Michael Hanselmann
    AssertEqual(names, utils.NiceSort(names))
541 288d6440 Michael Hanselmann
542 0fdf247d Michael Hanselmann
    # When requesting specific names, the order must be kept
543 0fdf247d Michael Hanselmann
    revnames = list(reversed(names))
544 0fdf247d Michael Hanselmann
    AssertEqual(namelist_fn(revnames), revnames)
545 288d6440 Michael Hanselmann
546 0fdf247d Michael Hanselmann
    randnames = list(names)
547 0fdf247d Michael Hanselmann
    rnd.shuffle(randnames)
548 0fdf247d Michael Hanselmann
    AssertEqual(namelist_fn(randnames), randnames)
549 288d6440 Michael Hanselmann
550 6d1e4845 Michael Hanselmann
  if test_unknown:
551 6d1e4845 Michael Hanselmann
    # Listing unknown items must fail
552 6d1e4845 Michael Hanselmann
    AssertCommand([cmd, "list", "this.name.certainly.does.not.exist"],
553 6d1e4845 Michael Hanselmann
                  fail=True)
554 2214cf14 Michael Hanselmann
555 2214cf14 Michael Hanselmann
  # Check exit code for listing unknown field
556 93146c8c Iustin Pop
  AssertEqual(AssertRedirectedCommand([cmd, "list",
557 93146c8c Iustin Pop
                                       "--output=field/does/not/exist"],
558 93146c8c Iustin Pop
                                      fail=True),
559 2214cf14 Michael Hanselmann
              constants.EXIT_UNKNOWN_FIELD)
560 2214cf14 Michael Hanselmann
561 2214cf14 Michael Hanselmann
562 2214cf14 Michael Hanselmann
def GenericQueryFieldsTest(cmd, fields):
563 2214cf14 Michael Hanselmann
  master = qa_config.GetMasterNode()
564 2214cf14 Michael Hanselmann
565 2214cf14 Michael Hanselmann
  # Listing fields
566 93146c8c Iustin Pop
  AssertRedirectedCommand([cmd, "list-fields"])
567 93146c8c Iustin Pop
  AssertRedirectedCommand([cmd, "list-fields"] + fields)
568 2214cf14 Michael Hanselmann
569 2214cf14 Michael Hanselmann
  # Check listed fields (all, must be sorted)
570 2214cf14 Michael Hanselmann
  realcmd = [cmd, "list-fields", "--separator=|", "--no-headers"]
571 aecba21e Michael Hanselmann
  output = GetCommandOutput(master.primary,
572 2214cf14 Michael Hanselmann
                            utils.ShellQuoteArgs(realcmd)).splitlines()
573 2214cf14 Michael Hanselmann
  AssertEqual([line.split("|", 1)[0] for line in output],
574 c694367b Michael Hanselmann
              utils.NiceSort(fields))
575 2214cf14 Michael Hanselmann
576 2214cf14 Michael Hanselmann
  # Check exit code for listing unknown field
577 2214cf14 Michael Hanselmann
  AssertEqual(AssertCommand([cmd, "list-fields", "field/does/not/exist"],
578 2214cf14 Michael Hanselmann
                            fail=True),
579 2214cf14 Michael Hanselmann
              constants.EXIT_UNKNOWN_FIELD)
580 2214cf14 Michael Hanselmann
581 288d6440 Michael Hanselmann
582 dfe11bad Michael Hanselmann
def _FormatWithColor(text, seq):
583 dfe11bad Michael Hanselmann
  if not seq:
584 dfe11bad Michael Hanselmann
    return text
585 dfe11bad Michael Hanselmann
  return "%s%s%s" % (seq, text, _RESET_SEQ)
586 23269c5b Michael Hanselmann
587 23269c5b Michael Hanselmann
588 dfe11bad Michael Hanselmann
FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
589 dfe11bad Michael Hanselmann
FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
590 dfe11bad Michael Hanselmann
FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
591 31fe5102 René Nussbaumer
592 31fe5102 René Nussbaumer
593 31fe5102 René Nussbaumer
def AddToEtcHosts(hostnames):
594 31fe5102 René Nussbaumer
  """Adds hostnames to /etc/hosts.
595 31fe5102 René Nussbaumer

596 31fe5102 René Nussbaumer
  @param hostnames: List of hostnames first used A records, all other CNAMEs
597 31fe5102 René Nussbaumer

598 31fe5102 René Nussbaumer
  """
599 31fe5102 René Nussbaumer
  master = qa_config.GetMasterNode()
600 aecba21e Michael Hanselmann
  tmp_hosts = UploadData(master.primary, "", mode=0644)
601 31fe5102 René Nussbaumer
602 31fe5102 René Nussbaumer
  data = []
603 31fe5102 René Nussbaumer
  for localhost in ("::1", "127.0.0.1"):
604 31fe5102 René Nussbaumer
    data.append("%s %s" % (localhost, " ".join(hostnames)))
605 31fe5102 René Nussbaumer
606 31fe5102 René Nussbaumer
  try:
607 48967eb0 Michael Hanselmann
    AssertCommand("{ cat %s && echo -e '%s'; } > %s && mv %s %s" %
608 48967eb0 Michael Hanselmann
                  (utils.ShellQuote(pathutils.ETC_HOSTS),
609 48967eb0 Michael Hanselmann
                   "\\n".join(data),
610 48967eb0 Michael Hanselmann
                   utils.ShellQuote(tmp_hosts),
611 48967eb0 Michael Hanselmann
                   utils.ShellQuote(tmp_hosts),
612 48967eb0 Michael Hanselmann
                   utils.ShellQuote(pathutils.ETC_HOSTS)))
613 48967eb0 Michael Hanselmann
  except Exception:
614 48967eb0 Michael Hanselmann
    AssertCommand(["rm", "-f", tmp_hosts])
615 48967eb0 Michael Hanselmann
    raise
616 31fe5102 René Nussbaumer
617 31fe5102 René Nussbaumer
618 31fe5102 René Nussbaumer
def RemoveFromEtcHosts(hostnames):
619 31fe5102 René Nussbaumer
  """Remove hostnames from /etc/hosts.
620 31fe5102 René Nussbaumer

621 31fe5102 René Nussbaumer
  @param hostnames: List of hostnames first used A records, all other CNAMEs
622 31fe5102 René Nussbaumer

623 31fe5102 René Nussbaumer
  """
624 31fe5102 René Nussbaumer
  master = qa_config.GetMasterNode()
625 aecba21e Michael Hanselmann
  tmp_hosts = UploadData(master.primary, "", mode=0644)
626 31fe5102 René Nussbaumer
  quoted_tmp_hosts = utils.ShellQuote(tmp_hosts)
627 31fe5102 René Nussbaumer
628 31fe5102 René Nussbaumer
  sed_data = " ".join(hostnames)
629 31fe5102 René Nussbaumer
  try:
630 fa0a1365 Michele Tartara
    AssertCommand((r"sed -e '/^\(::1\|127\.0\.0\.1\)\s\+%s/d' %s > %s"
631 fa0a1365 Michele Tartara
                   r" && mv %s %s") %
632 48967eb0 Michael Hanselmann
                   (sed_data, utils.ShellQuote(pathutils.ETC_HOSTS),
633 48967eb0 Michael Hanselmann
                    quoted_tmp_hosts, quoted_tmp_hosts,
634 48967eb0 Michael Hanselmann
                    utils.ShellQuote(pathutils.ETC_HOSTS)))
635 48967eb0 Michael Hanselmann
  except Exception:
636 48967eb0 Michael Hanselmann
    AssertCommand(["rm", "-f", tmp_hosts])
637 48967eb0 Michael Hanselmann
    raise
638 c9e05005 Michael Hanselmann
639 c9e05005 Michael Hanselmann
640 c9e05005 Michael Hanselmann
def RunInstanceCheck(instance, running):
641 c9e05005 Michael Hanselmann
  """Check if instance is running or not.
642 c9e05005 Michael Hanselmann

643 c9e05005 Michael Hanselmann
  """
644 6998aefe Michael Hanselmann
  instance_name = _GetName(instance, operator.attrgetter("name"))
645 2ac35588 Michael Hanselmann
646 c9e05005 Michael Hanselmann
  script = qa_config.GetInstanceCheckScript()
647 c9e05005 Michael Hanselmann
  if not script:
648 c9e05005 Michael Hanselmann
    return
649 c9e05005 Michael Hanselmann
650 c9e05005 Michael Hanselmann
  master_node = qa_config.GetMasterNode()
651 c9e05005 Michael Hanselmann
652 c9e05005 Michael Hanselmann
  # Build command to connect to master node
653 aecba21e Michael Hanselmann
  master_ssh = GetSSHCommand(master_node.primary, "--")
654 c9e05005 Michael Hanselmann
655 c9e05005 Michael Hanselmann
  if running:
656 c9e05005 Michael Hanselmann
    running_shellval = "1"
657 c9e05005 Michael Hanselmann
    running_text = ""
658 c9e05005 Michael Hanselmann
  else:
659 c9e05005 Michael Hanselmann
    running_shellval = ""
660 c9e05005 Michael Hanselmann
    running_text = "not "
661 c9e05005 Michael Hanselmann
662 c9e05005 Michael Hanselmann
  print FormatInfo("Checking if instance '%s' is %srunning" %
663 c9e05005 Michael Hanselmann
                   (instance_name, running_text))
664 c9e05005 Michael Hanselmann
665 c9e05005 Michael Hanselmann
  args = [script, instance_name]
666 c9e05005 Michael Hanselmann
  env = {
667 c9e05005 Michael Hanselmann
    "PATH": constants.HOOKS_PATH,
668 c9e05005 Michael Hanselmann
    "RUN_UUID": _RUN_UUID,
669 c9e05005 Michael Hanselmann
    "MASTER_SSH": utils.ShellQuoteArgs(master_ssh),
670 c9e05005 Michael Hanselmann
    "INSTANCE_NAME": instance_name,
671 c9e05005 Michael Hanselmann
    "INSTANCE_RUNNING": running_shellval,
672 c9e05005 Michael Hanselmann
    }
673 c9e05005 Michael Hanselmann
674 c9e05005 Michael Hanselmann
  result = os.spawnve(os.P_WAIT, script, args, env)
675 c9e05005 Michael Hanselmann
  if result != 0:
676 c9e05005 Michael Hanselmann
    raise qa_error.Error("Instance check failed with result %s" % result)
677 c9e05005 Michael Hanselmann
678 c9e05005 Michael Hanselmann
679 c9e05005 Michael Hanselmann
def _InstanceCheckInner(expected, instarg, args, result):
680 c9e05005 Michael Hanselmann
  """Helper function used by L{InstanceCheck}.
681 c9e05005 Michael Hanselmann

682 c9e05005 Michael Hanselmann
  """
683 c9e05005 Michael Hanselmann
  if instarg == FIRST_ARG:
684 c9e05005 Michael Hanselmann
    instance = args[0]
685 c9e05005 Michael Hanselmann
  elif instarg == RETURN_VALUE:
686 c9e05005 Michael Hanselmann
    instance = result
687 c9e05005 Michael Hanselmann
  else:
688 c9e05005 Michael Hanselmann
    raise Exception("Invalid value '%s' for instance argument" % instarg)
689 c9e05005 Michael Hanselmann
690 c9e05005 Michael Hanselmann
  if expected in (INST_DOWN, INST_UP):
691 c9e05005 Michael Hanselmann
    RunInstanceCheck(instance, (expected == INST_UP))
692 c9e05005 Michael Hanselmann
  elif expected is not None:
693 c9e05005 Michael Hanselmann
    raise Exception("Invalid value '%s'" % expected)
694 c9e05005 Michael Hanselmann
695 c9e05005 Michael Hanselmann
696 c9e05005 Michael Hanselmann
def InstanceCheck(before, after, instarg):
697 c9e05005 Michael Hanselmann
  """Decorator to check instance status before and after test.
698 c9e05005 Michael Hanselmann

699 c9e05005 Michael Hanselmann
  @param before: L{INST_DOWN} if instance must be stopped before test,
700 c9e05005 Michael Hanselmann
    L{INST_UP} if instance must be running before test, L{None} to not check.
701 c9e05005 Michael Hanselmann
  @param after: L{INST_DOWN} if instance must be stopped after test,
702 c9e05005 Michael Hanselmann
    L{INST_UP} if instance must be running after test, L{None} to not check.
703 c9e05005 Michael Hanselmann
  @param instarg: L{FIRST_ARG} to use first argument to test as instance (a
704 c9e05005 Michael Hanselmann
    dictionary), L{RETURN_VALUE} to use return value (disallows pre-checks)
705 c9e05005 Michael Hanselmann

706 c9e05005 Michael Hanselmann
  """
707 c9e05005 Michael Hanselmann
  def decorator(fn):
708 c9e05005 Michael Hanselmann
    @functools.wraps(fn)
709 c9e05005 Michael Hanselmann
    def wrapper(*args, **kwargs):
710 c9e05005 Michael Hanselmann
      _InstanceCheckInner(before, instarg, args, NotImplemented)
711 c9e05005 Michael Hanselmann
712 c9e05005 Michael Hanselmann
      result = fn(*args, **kwargs)
713 c9e05005 Michael Hanselmann
714 c9e05005 Michael Hanselmann
      _InstanceCheckInner(after, instarg, args, result)
715 c9e05005 Michael Hanselmann
716 c9e05005 Michael Hanselmann
      return result
717 c9e05005 Michael Hanselmann
    return wrapper
718 c9e05005 Michael Hanselmann
  return decorator
719 b4d2d2cb Michael Hanselmann
720 b4d2d2cb Michael Hanselmann
721 b4d2d2cb Michael Hanselmann
def GetNonexistentGroups(count):
722 b4d2d2cb Michael Hanselmann
  """Gets group names which shouldn't exist on the cluster.
723 b4d2d2cb Michael Hanselmann

724 b4d2d2cb Michael Hanselmann
  @param count: Number of groups to get
725 ea7693c1 Helga Velroyen
  @rtype: integer
726 b4d2d2cb Michael Hanselmann

727 b4d2d2cb Michael Hanselmann
  """
728 ea7693c1 Helga Velroyen
  return GetNonexistentEntityNames(count, "groups", "group")
729 b4d2d2cb Michael Hanselmann
730 ea7693c1 Helga Velroyen
731 ea7693c1 Helga Velroyen
def GetNonexistentEntityNames(count, name_config, name_prefix):
732 ea7693c1 Helga Velroyen
  """Gets entity names which shouldn't exist on the cluster.
733 ea7693c1 Helga Velroyen

734 ea7693c1 Helga Velroyen
  The actualy names can refer to arbitrary entities (for example
735 ea7693c1 Helga Velroyen
  groups, networks).
736 ea7693c1 Helga Velroyen

737 ea7693c1 Helga Velroyen
  @param count: Number of names to get
738 ea7693c1 Helga Velroyen
  @rtype: integer
739 ea7693c1 Helga Velroyen
  @param name_config: name of the leaf in the config containing
740 ea7693c1 Helga Velroyen
    this entity's configuration, including a 'inexistent-'
741 ea7693c1 Helga Velroyen
    element
742 ea7693c1 Helga Velroyen
  @rtype: string
743 ea7693c1 Helga Velroyen
  @param name_prefix: prefix of the entity's names, used to compose
744 ea7693c1 Helga Velroyen
    the default values; for example for groups, the prefix is
745 ea7693c1 Helga Velroyen
    'group' and the generated names are then group1, group2, ...
746 ea7693c1 Helga Velroyen
  @rtype: string
747 ea7693c1 Helga Velroyen

748 ea7693c1 Helga Velroyen
  """
749 ea7693c1 Helga Velroyen
  entities = qa_config.get(name_config, {})
750 ea7693c1 Helga Velroyen
751 ea7693c1 Helga Velroyen
  default = [name_prefix + str(i) for i in range(count)]
752 b4d2d2cb Michael Hanselmann
  assert count <= len(default)
753 b4d2d2cb Michael Hanselmann
754 ea7693c1 Helga Velroyen
  name_config_inexistent = "inexistent-" + name_config
755 ea7693c1 Helga Velroyen
  candidates = entities.get(name_config_inexistent, default)[:count]
756 b4d2d2cb Michael Hanselmann
757 b4d2d2cb Michael Hanselmann
  if len(candidates) < count:
758 ea7693c1 Helga Velroyen
    raise Exception("At least %s non-existent %s are needed" %
759 ea7693c1 Helga Velroyen
                    (count, name_config))
760 b4d2d2cb Michael Hanselmann
761 b4d2d2cb Michael Hanselmann
  return candidates
762 7160f14a Michael Hanselmann
763 7160f14a Michael Hanselmann
764 7160f14a Michael Hanselmann
def MakeNodePath(node, path):
765 7160f14a Michael Hanselmann
  """Builds an absolute path for a virtual node.
766 7160f14a Michael Hanselmann

767 7160f14a Michael Hanselmann
  @type node: string or L{qa_config._QaNode}
768 7160f14a Michael Hanselmann
  @param node: Node
769 7160f14a Michael Hanselmann
  @type path: string
770 7160f14a Michael Hanselmann
  @param path: Path without node-specific prefix
771 7160f14a Michael Hanselmann

772 7160f14a Michael Hanselmann
  """
773 7160f14a Michael Hanselmann
  (_, basedir) = qa_config.GetVclusterSettings()
774 7160f14a Michael Hanselmann
775 7160f14a Michael Hanselmann
  if isinstance(node, basestring):
776 7160f14a Michael Hanselmann
    name = node
777 7160f14a Michael Hanselmann
  else:
778 7160f14a Michael Hanselmann
    name = node.primary
779 7160f14a Michael Hanselmann
780 7160f14a Michael Hanselmann
  if basedir:
781 7160f14a Michael Hanselmann
    assert path.startswith("/")
782 7160f14a Michael Hanselmann
    return "%s%s" % (vcluster.MakeNodeRoot(basedir, name), path)
783 7160f14a Michael Hanselmann
  else:
784 7160f14a Michael Hanselmann
    return path
785 63e08b25 Bernardo Dal Seno
786 63e08b25 Bernardo Dal Seno
787 ec996117 Bernardo Dal Seno
def _GetParameterOptions(specs):
788 63e08b25 Bernardo Dal Seno
  """Helper to build policy options."""
789 ec996117 Bernardo Dal Seno
  values = ["%s=%s" % (par, val)
790 ec996117 Bernardo Dal Seno
            for (par, val) in specs.items()]
791 63e08b25 Bernardo Dal Seno
  return ",".join(values)
792 63e08b25 Bernardo Dal Seno
793 63e08b25 Bernardo Dal Seno
794 ec996117 Bernardo Dal Seno
def TestSetISpecs(new_specs=None, diff_specs=None, get_policy_fn=None,
795 ec996117 Bernardo Dal Seno
                  build_cmd_fn=None, fail=False, old_values=None):
796 63e08b25 Bernardo Dal Seno
  """Change instance specs for an object.
797 63e08b25 Bernardo Dal Seno

798 ec996117 Bernardo Dal Seno
  At most one of new_specs or diff_specs can be specified.
799 ec996117 Bernardo Dal Seno

800 ec996117 Bernardo Dal Seno
  @type new_specs: dict
801 ec996117 Bernardo Dal Seno
  @param new_specs: new complete specs, in the same format returned by
802 ec996117 Bernardo Dal Seno
      L{ParseIPolicy}.
803 ec996117 Bernardo Dal Seno
  @type diff_specs: dict
804 7c8ae421 Bernardo Dal Seno
  @param diff_specs: partial specs, it can be an incomplete specifications, but
805 7c8ae421 Bernardo Dal Seno
      if min/max specs are specified, their number must match the number of the
806 7c8ae421 Bernardo Dal Seno
      existing specs
807 63e08b25 Bernardo Dal Seno
  @type get_policy_fn: function
808 63e08b25 Bernardo Dal Seno
  @param get_policy_fn: function that returns the current policy as in
809 ec996117 Bernardo Dal Seno
      L{ParseIPolicy}
810 63e08b25 Bernardo Dal Seno
  @type build_cmd_fn: function
811 63e08b25 Bernardo Dal Seno
  @param build_cmd_fn: function that return the full command line from the
812 63e08b25 Bernardo Dal Seno
      options alone
813 63e08b25 Bernardo Dal Seno
  @type fail: bool
814 63e08b25 Bernardo Dal Seno
  @param fail: if the change is expected to fail
815 63e08b25 Bernardo Dal Seno
  @type old_values: tuple
816 63e08b25 Bernardo Dal Seno
  @param old_values: (old_policy, old_specs), as returned by
817 ec996117 Bernardo Dal Seno
     L{ParseIPolicy}
818 ec996117 Bernardo Dal Seno
  @return: same as L{ParseIPolicy}
819 63e08b25 Bernardo Dal Seno

820 63e08b25 Bernardo Dal Seno
  """
821 63e08b25 Bernardo Dal Seno
  assert get_policy_fn is not None
822 63e08b25 Bernardo Dal Seno
  assert build_cmd_fn is not None
823 ec996117 Bernardo Dal Seno
  assert new_specs is None or diff_specs is None
824 63e08b25 Bernardo Dal Seno
825 63e08b25 Bernardo Dal Seno
  if old_values:
826 63e08b25 Bernardo Dal Seno
    (old_policy, old_specs) = old_values
827 63e08b25 Bernardo Dal Seno
  else:
828 63e08b25 Bernardo Dal Seno
    (old_policy, old_specs) = get_policy_fn()
829 ec996117 Bernardo Dal Seno
830 ec996117 Bernardo Dal Seno
  if diff_specs:
831 ec996117 Bernardo Dal Seno
    new_specs = copy.deepcopy(old_specs)
832 7c8ae421 Bernardo Dal Seno
    if constants.ISPECS_MINMAX in diff_specs:
833 7c8ae421 Bernardo Dal Seno
      AssertEqual(len(new_specs[constants.ISPECS_MINMAX]),
834 7c8ae421 Bernardo Dal Seno
                  len(diff_specs[constants.ISPECS_MINMAX]))
835 7c8ae421 Bernardo Dal Seno
      for (new_minmax, diff_minmax) in zip(new_specs[constants.ISPECS_MINMAX],
836 7c8ae421 Bernardo Dal Seno
                                           diff_specs[constants.ISPECS_MINMAX]):
837 7c8ae421 Bernardo Dal Seno
        for (key, parvals) in diff_minmax.items():
838 7c8ae421 Bernardo Dal Seno
          for (par, val) in parvals.items():
839 7c8ae421 Bernardo Dal Seno
            new_minmax[key][par] = val
840 7c8ae421 Bernardo Dal Seno
    for (par, val) in diff_specs.get(constants.ISPECS_STD, {}).items():
841 7c8ae421 Bernardo Dal Seno
      new_specs[constants.ISPECS_STD][par] = val
842 ec996117 Bernardo Dal Seno
843 63e08b25 Bernardo Dal Seno
  if new_specs:
844 63e08b25 Bernardo Dal Seno
    cmd = []
845 7c8ae421 Bernardo Dal Seno
    if (diff_specs is None or constants.ISPECS_MINMAX in diff_specs):
846 63e08b25 Bernardo Dal Seno
      minmax_opt_items = []
847 7c8ae421 Bernardo Dal Seno
      for minmax in new_specs[constants.ISPECS_MINMAX]:
848 7c8ae421 Bernardo Dal Seno
        minmax_opts = []
849 7c8ae421 Bernardo Dal Seno
        for key in ["min", "max"]:
850 7c8ae421 Bernardo Dal Seno
          keyopt = _GetParameterOptions(minmax[key])
851 7c8ae421 Bernardo Dal Seno
          minmax_opts.append("%s:%s" % (key, keyopt))
852 7c8ae421 Bernardo Dal Seno
        minmax_opt_items.append("/".join(minmax_opts))
853 63e08b25 Bernardo Dal Seno
      cmd.extend([
854 63e08b25 Bernardo Dal Seno
        "--ipolicy-bounds-specs",
855 7c8ae421 Bernardo Dal Seno
        "//".join(minmax_opt_items)
856 63e08b25 Bernardo Dal Seno
        ])
857 7c8ae421 Bernardo Dal Seno
    if diff_specs is None:
858 ec996117 Bernardo Dal Seno
      std_source = new_specs
859 7c8ae421 Bernardo Dal Seno
    else:
860 7c8ae421 Bernardo Dal Seno
      std_source = diff_specs
861 ec996117 Bernardo Dal Seno
    std_opt = _GetParameterOptions(std_source.get("std", {}))
862 63e08b25 Bernardo Dal Seno
    if std_opt:
863 63e08b25 Bernardo Dal Seno
      cmd.extend(["--ipolicy-std-specs", std_opt])
864 63e08b25 Bernardo Dal Seno
    AssertCommand(build_cmd_fn(cmd), fail=fail)
865 63e08b25 Bernardo Dal Seno
866 ec996117 Bernardo Dal Seno
    # Check the new state
867 ec996117 Bernardo Dal Seno
    (eff_policy, eff_specs) = get_policy_fn()
868 ec996117 Bernardo Dal Seno
    AssertEqual(eff_policy, old_policy)
869 ec996117 Bernardo Dal Seno
    if fail:
870 ec996117 Bernardo Dal Seno
      AssertEqual(eff_specs, old_specs)
871 ec996117 Bernardo Dal Seno
    else:
872 ec996117 Bernardo Dal Seno
      AssertEqual(eff_specs, new_specs)
873 ec996117 Bernardo Dal Seno
874 63e08b25 Bernardo Dal Seno
  else:
875 ec996117 Bernardo Dal Seno
    (eff_policy, eff_specs) = (old_policy, old_specs)
876 ec996117 Bernardo Dal Seno
877 63e08b25 Bernardo Dal Seno
  return (eff_policy, eff_specs)
878 63e08b25 Bernardo Dal Seno
879 63e08b25 Bernardo Dal Seno
880 63e08b25 Bernardo Dal Seno
def ParseIPolicy(policy):
881 63e08b25 Bernardo Dal Seno
  """Parse and split instance an instance policy.
882 63e08b25 Bernardo Dal Seno

883 63e08b25 Bernardo Dal Seno
  @type policy: dict
884 63e08b25 Bernardo Dal Seno
  @param policy: policy, as returned by L{GetObjectInfo}
885 63e08b25 Bernardo Dal Seno
  @rtype: tuple
886 63e08b25 Bernardo Dal Seno
  @return: (policy, specs), where:
887 63e08b25 Bernardo Dal Seno
      - policy is a dictionary of the policy values, instance specs excluded
888 7c8ae421 Bernardo Dal Seno
      - specs is a dictionary containing only the specs, using the internal
889 7c8ae421 Bernardo Dal Seno
        format (see L{constants.IPOLICY_DEFAULTS} for an example)
890 63e08b25 Bernardo Dal Seno

891 63e08b25 Bernardo Dal Seno
  """
892 63e08b25 Bernardo Dal Seno
  ret_specs = {}
893 63e08b25 Bernardo Dal Seno
  ret_policy = {}
894 63e08b25 Bernardo Dal Seno
  for (key, val) in policy.items():
895 7c8ae421 Bernardo Dal Seno
    if key == "bounds specs":
896 7c8ae421 Bernardo Dal Seno
      ret_specs[constants.ISPECS_MINMAX] = []
897 7c8ae421 Bernardo Dal Seno
      for minmax in val:
898 7c8ae421 Bernardo Dal Seno
        ret_minmax = {}
899 7c8ae421 Bernardo Dal Seno
        for key in minmax:
900 7c8ae421 Bernardo Dal Seno
          keyparts = key.split("/", 1)
901 7c8ae421 Bernardo Dal Seno
          assert len(keyparts) > 1
902 7c8ae421 Bernardo Dal Seno
          ret_minmax[keyparts[0]] = minmax[key]
903 7c8ae421 Bernardo Dal Seno
        ret_specs[constants.ISPECS_MINMAX].append(ret_minmax)
904 7c8ae421 Bernardo Dal Seno
    elif key == constants.ISPECS_STD:
905 ec996117 Bernardo Dal Seno
      ret_specs[key] = val
906 63e08b25 Bernardo Dal Seno
    else:
907 63e08b25 Bernardo Dal Seno
      ret_policy[key] = val
908 63e08b25 Bernardo Dal Seno
  return (ret_policy, ret_specs)