Statistics
| Branch: | Tag: | Revision:

root / qa / qa_utils.py @ 2f4b4f78

History | View | Annotate | Download (7.2 kB)

1
#
2
#
3

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

    
21

    
22
"""Utilities for QA tests.
23

24
"""
25

    
26
import os
27
import re
28
import sys
29
import subprocess
30

    
31
from ganeti import utils
32

    
33
import qa_config
34
import qa_error
35

    
36

    
37
_INFO_SEQ = None
38
_WARNING_SEQ = None
39
_ERROR_SEQ = None
40
_RESET_SEQ = None
41

    
42

    
43
def _SetupColours():
44
  """Initializes the colour constants.
45

46
  """
47
  global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
48

    
49
  # Don't use colours if stdout isn't a terminal
50
  if not sys.stdout.isatty():
51
    return
52

    
53
  try:
54
    import curses
55
  except ImportError:
56
    # Don't use colours if curses module can't be imported
57
    return
58

    
59
  curses.setupterm()
60

    
61
  _RESET_SEQ = curses.tigetstr("op")
62

    
63
  setaf = curses.tigetstr("setaf")
64
  _INFO_SEQ = curses.tparm(setaf, curses.COLOR_GREEN)
65
  _WARNING_SEQ = curses.tparm(setaf, curses.COLOR_YELLOW)
66
  _ERROR_SEQ = curses.tparm(setaf, curses.COLOR_RED)
67

    
68

    
69
_SetupColours()
70

    
71

    
72
def AssertIn(item, sequence):
73
  """Raises an error when item is not in sequence.
74

75
  """
76
  if item not in sequence:
77
    raise qa_error.Error('%r not in %r' % (item, sequence))
78

    
79

    
80
def AssertEqual(first, second):
81
  """Raises an error when values aren't equal.
82

83
  """
84
  if not first == second:
85
    raise qa_error.Error('%r == %r' % (first, second))
86

    
87

    
88
def AssertNotEqual(first, second):
89
  """Raises an error when values are equal.
90

91
  """
92
  if not first != second:
93
    raise qa_error.Error('%r != %r' % (first, second))
94

    
95

    
96
def AssertMatch(string, pattern):
97
  """Raises an error when string doesn't match regexp pattern.
98

99
  """
100
  if not re.match(pattern, string):
101
    raise qa_error.Error("%r doesn't match /%r/" % (string, pattern))
102

    
103

    
104
def AssertCommand(cmd, fail=False, node=None):
105
  """Checks that a remote command succeeds.
106

107
  @param cmd: either a string (the command to execute) or a list (to
108
      be converted using L{utils.ShellQuoteArgs} into a string)
109
  @type fail: boolean
110
  @param fail: if the command is expected to fail instead of succeeding
111
  @param node: if passed, it should be the node on which the command
112
      should be executed, instead of the master node (can be either a
113
      dict or a string)
114

115
  """
116
  if node is None:
117
    node = qa_config.GetMasterNode()
118

    
119
  if isinstance(node, basestring):
120
    nodename = node
121
  else:
122
    nodename = node["primary"]
123

    
124
  if isinstance(cmd, basestring):
125
    cmdstr = cmd
126
  else:
127
    cmdstr = utils.ShellQuoteArgs(cmd)
128

    
129
  rcode = StartSSH(nodename, cmdstr).wait()
130

    
131
  if fail:
132
    if rcode == 0:
133
      raise qa_error.Error("Command '%s' on node %s was expected to fail but"
134
                           " didn't" % (cmdstr, nodename))
135
  else:
136
    if rcode != 0:
137
      raise qa_error.Error("Command '%s' on node %s failed, exit code %s" %
138
                           (cmdstr, nodename, rcode))
139

    
140

    
141
def GetSSHCommand(node, cmd, strict=True):
142
  """Builds SSH command to be executed.
143

144
  Args:
145
  - node: Node the command should run on
146
  - cmd: Command to be executed as a list with all parameters
147
  - strict: Whether to enable strict host key checking
148

149
  """
150
  args = [ 'ssh', '-oEscapeChar=none', '-oBatchMode=yes', '-l', 'root', '-t' ]
151

    
152
  if strict:
153
    tmp = 'yes'
154
  else:
155
    tmp = 'no'
156
  args.append('-oStrictHostKeyChecking=%s' % tmp)
157
  args.append('-oClearAllForwardings=yes')
158
  args.append('-oForwardAgent=yes')
159
  args.append(node)
160
  args.append(cmd)
161

    
162
  return args
163

    
164

    
165
def StartLocalCommand(cmd, **kwargs):
166
  """Starts a local command.
167

168
  """
169
  print "Command: %s" % utils.ShellQuoteArgs(cmd)
170
  return subprocess.Popen(cmd, shell=False, **kwargs)
171

    
172

    
173
def StartSSH(node, cmd, strict=True):
174
  """Starts SSH.
175

176
  """
177
  return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict))
178

    
179

    
180
def GetCommandOutput(node, cmd):
181
  """Returns the output of a command executed on the given node.
182

183
  """
184
  p = StartLocalCommand(GetSSHCommand(node, cmd), stdout=subprocess.PIPE)
185
  AssertEqual(p.wait(), 0)
186
  return p.stdout.read()
187

    
188

    
189
def UploadFile(node, src):
190
  """Uploads a file to a node and returns the filename.
191

192
  Caller needs to remove the returned file on the node when it's not needed
193
  anymore.
194

195
  """
196
  # Make sure nobody else has access to it while preserving local permissions
197
  mode = os.stat(src).st_mode & 0700
198

    
199
  cmd = ('tmp=$(tempfile --mode %o --prefix gnt) && '
200
         '[[ -f "${tmp}" ]] && '
201
         'cat > "${tmp}" && '
202
         'echo "${tmp}"') % mode
203

    
204
  f = open(src, 'r')
205
  try:
206
    p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
207
                         stdout=subprocess.PIPE)
208
    AssertEqual(p.wait(), 0)
209

    
210
    # Return temporary filename
211
    return p.stdout.read().strip()
212
  finally:
213
    f.close()
214

    
215

    
216
def BackupFile(node, path):
217
  """Creates a backup of a file on the node and returns the filename.
218

219
  Caller needs to remove the returned file on the node when it's not needed
220
  anymore.
221

222
  """
223
  cmd = ("tmp=$(tempfile --prefix .gnt --directory=$(dirname %s)) && "
224
         "[[ -f \"$tmp\" ]] && "
225
         "cp %s $tmp && "
226
         "echo $tmp") % (utils.ShellQuote(path), utils.ShellQuote(path))
227

    
228
  # Return temporary filename
229
  return GetCommandOutput(node, cmd).strip()
230

    
231

    
232
def _ResolveName(cmd, key):
233
  """Helper function.
234

235
  """
236
  master = qa_config.GetMasterNode()
237

    
238
  output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
239
  for line in output.splitlines():
240
    (lkey, lvalue) = line.split(':', 1)
241
    if lkey == key:
242
      return lvalue.lstrip()
243
  raise KeyError("Key not found")
244

    
245

    
246
def ResolveInstanceName(instance):
247
  """Gets the full name of an instance.
248

249
  @type instance: string
250
  @param instance: Instance name
251

252
  """
253
  return _ResolveName(['gnt-instance', 'info', instance],
254
                      'Instance name')
255

    
256

    
257
def ResolveNodeName(node):
258
  """Gets the full name of a node.
259

260
  """
261
  return _ResolveName(['gnt-node', 'info', node['primary']],
262
                      'Node name')
263

    
264

    
265
def GetNodeInstances(node, secondaries=False):
266
  """Gets a list of instances on a node.
267

268
  """
269
  master = qa_config.GetMasterNode()
270
  node_name = ResolveNodeName(node)
271

    
272
  # Get list of all instances
273
  cmd = ['gnt-instance', 'list', '--separator=:', '--no-headers',
274
         '--output=name,pnode,snodes']
275
  output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
276

    
277
  instances = []
278
  for line in output.splitlines():
279
    (name, pnode, snodes) = line.split(':', 2)
280
    if ((not secondaries and pnode == node_name) or
281
        (secondaries and node_name in snodes.split(','))):
282
      instances.append(name)
283

    
284
  return instances
285

    
286

    
287
def _FormatWithColor(text, seq):
288
  if not seq:
289
    return text
290
  return "%s%s%s" % (seq, text, _RESET_SEQ)
291

    
292

    
293
FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
294
FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
295
FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)