Statistics
| Branch: | Tag: | Revision:

root / qa / qa_utils.py @ 5d9d1aff

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 cec9845c Michael Hanselmann
import qa_config
49 cec9845c Michael Hanselmann
import qa_error
50 cec9845c Michael Hanselmann
51 cec9845c Michael Hanselmann
52 23269c5b Michael Hanselmann
_INFO_SEQ = None
53 23269c5b Michael Hanselmann
_WARNING_SEQ = None
54 23269c5b Michael Hanselmann
_ERROR_SEQ = None
55 23269c5b Michael Hanselmann
_RESET_SEQ = None
56 23269c5b Michael Hanselmann
57 f7e6f3c8 Iustin Pop
_MULTIPLEXERS = {}
58 f7e6f3c8 Iustin Pop
59 c9e05005 Michael Hanselmann
#: Unique ID per QA run
60 c9e05005 Michael Hanselmann
_RUN_UUID = utils.NewUUID()
61 c9e05005 Michael Hanselmann
62 afd5ca04 Iustin Pop
#: Path to the QA query output log file
63 afd5ca04 Iustin Pop
_QA_OUTPUT = pathutils.GetLogFilename("qa-output")
64 afd5ca04 Iustin Pop
65 c9e05005 Michael Hanselmann
66 c9e05005 Michael Hanselmann
(INST_DOWN,
67 c9e05005 Michael Hanselmann
 INST_UP) = range(500, 502)
68 c9e05005 Michael Hanselmann
69 c9e05005 Michael Hanselmann
(FIRST_ARG,
70 c9e05005 Michael Hanselmann
 RETURN_VALUE) = range(1000, 1002)
71 c9e05005 Michael Hanselmann
72 23269c5b Michael Hanselmann
73 23269c5b Michael Hanselmann
def _SetupColours():
74 23269c5b Michael Hanselmann
  """Initializes the colour constants.
75 23269c5b Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

303 f7e6f3c8 Iustin Pop
  @param node: the node for which to open the multiplexer
304 f7e6f3c8 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

443 46f9a948 Michael Hanselmann
  @type instance: string
444 46f9a948 Michael Hanselmann
  @param instance: Instance name
445 46f9a948 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

796 ec996117 Bernardo Dal Seno
  At most one of new_specs or diff_specs can be specified.
797 ec996117 Bernardo Dal Seno

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

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

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

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