root / qa / qa_utils.py @ 1abbbbe2
History | View | Annotate | Download (7 kB)
1 | c68d1f43 | Michael Hanselmann | #
|
---|---|---|---|
2 | c68d1f43 | Michael Hanselmann | #
|
3 | c68d1f43 | Michael Hanselmann | |
4 | cec9845c | Michael Hanselmann | # Copyright (C) 2007 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 | cec9845c | Michael Hanselmann | import os |
27 | 23269c5b | Michael Hanselmann | import sys |
28 | cec9845c | Michael Hanselmann | import subprocess |
29 | cec9845c | Michael Hanselmann | |
30 | cec9845c | Michael Hanselmann | from ganeti import utils |
31 | cec9845c | Michael Hanselmann | |
32 | cec9845c | Michael Hanselmann | import qa_config |
33 | cec9845c | Michael Hanselmann | import qa_error |
34 | cec9845c | Michael Hanselmann | |
35 | cec9845c | Michael Hanselmann | |
36 | 23269c5b | Michael Hanselmann | _INFO_SEQ = None
|
37 | 23269c5b | Michael Hanselmann | _WARNING_SEQ = None
|
38 | 23269c5b | Michael Hanselmann | _ERROR_SEQ = None
|
39 | 23269c5b | Michael Hanselmann | _RESET_SEQ = None
|
40 | 23269c5b | Michael Hanselmann | |
41 | 23269c5b | Michael Hanselmann | |
42 | 1672a0d1 | Michael Hanselmann | # List of all hooks
|
43 | 1672a0d1 | Michael Hanselmann | _hooks = [] |
44 | 1672a0d1 | Michael Hanselmann | |
45 | 1672a0d1 | Michael Hanselmann | |
46 | 23269c5b | Michael Hanselmann | def _SetupColours(): |
47 | 23269c5b | Michael Hanselmann | """Initializes the colour constants.
|
48 | 23269c5b | Michael Hanselmann |
|
49 | 23269c5b | Michael Hanselmann | """
|
50 | 23269c5b | Michael Hanselmann | global _INFO_SEQ, _WARNING_SEQ, _ERROR_SEQ, _RESET_SEQ
|
51 | 23269c5b | Michael Hanselmann | |
52 | dfe11bad | Michael Hanselmann | # Don't use colours if stdout isn't a terminal
|
53 | dfe11bad | Michael Hanselmann | if not sys.stdout.isatty(): |
54 | dfe11bad | Michael Hanselmann | return
|
55 | dfe11bad | Michael Hanselmann | |
56 | 23269c5b | Michael Hanselmann | try:
|
57 | 23269c5b | Michael Hanselmann | import curses |
58 | 23269c5b | Michael Hanselmann | except ImportError: |
59 | 23269c5b | Michael Hanselmann | # Don't use colours if curses module can't be imported
|
60 | 23269c5b | Michael Hanselmann | return
|
61 | 23269c5b | Michael Hanselmann | |
62 | 23269c5b | Michael Hanselmann | curses.setupterm() |
63 | 23269c5b | Michael Hanselmann | |
64 | 23269c5b | Michael Hanselmann | _RESET_SEQ = curses.tigetstr("op")
|
65 | 23269c5b | Michael Hanselmann | |
66 | 23269c5b | Michael Hanselmann | setaf = curses.tigetstr("setaf")
|
67 | 23269c5b | Michael Hanselmann | _INFO_SEQ = curses.tparm(setaf, curses.COLOR_GREEN) |
68 | 23269c5b | Michael Hanselmann | _WARNING_SEQ = curses.tparm(setaf, curses.COLOR_YELLOW) |
69 | 23269c5b | Michael Hanselmann | _ERROR_SEQ = curses.tparm(setaf, curses.COLOR_RED) |
70 | 23269c5b | Michael Hanselmann | |
71 | 23269c5b | Michael Hanselmann | |
72 | 23269c5b | Michael Hanselmann | _SetupColours() |
73 | 23269c5b | Michael Hanselmann | |
74 | 23269c5b | Michael Hanselmann | |
75 | e8ae0c20 | Michael Hanselmann | def AssertEqual(first, second): |
76 | cec9845c | Michael Hanselmann | """Raises an error when values aren't equal.
|
77 | cec9845c | Michael Hanselmann |
|
78 | cec9845c | Michael Hanselmann | """
|
79 | cec9845c | Michael Hanselmann | if not first == second: |
80 | e8ae0c20 | Michael Hanselmann | raise qa_error.Error('%r == %r' % (first, second)) |
81 | e8ae0c20 | Michael Hanselmann | |
82 | e8ae0c20 | Michael Hanselmann | |
83 | e8ae0c20 | Michael Hanselmann | def AssertNotEqual(first, second): |
84 | e8ae0c20 | Michael Hanselmann | """Raises an error when values are equal.
|
85 | e8ae0c20 | Michael Hanselmann |
|
86 | e8ae0c20 | Michael Hanselmann | """
|
87 | e8ae0c20 | Michael Hanselmann | if not first != second: |
88 | e8ae0c20 | Michael Hanselmann | raise qa_error.Error('%r != %r' % (first, second)) |
89 | cec9845c | Michael Hanselmann | |
90 | cec9845c | Michael Hanselmann | |
91 | cec9845c | Michael Hanselmann | def GetSSHCommand(node, cmd, strict=True): |
92 | cec9845c | Michael Hanselmann | """Builds SSH command to be executed.
|
93 | cec9845c | Michael Hanselmann |
|
94 | c68d1f43 | Michael Hanselmann | Args:
|
95 | c68d1f43 | Michael Hanselmann | - node: Node the command should run on
|
96 | c68d1f43 | Michael Hanselmann | - cmd: Command to be executed as a list with all parameters
|
97 | c68d1f43 | Michael Hanselmann | - strict: Whether to enable strict host key checking
|
98 | c68d1f43 | Michael Hanselmann |
|
99 | cec9845c | Michael Hanselmann | """
|
100 | cec9845c | Michael Hanselmann | args = [ 'ssh', '-oEscapeChar=none', '-oBatchMode=yes', '-l', 'root' ] |
101 | cec9845c | Michael Hanselmann | |
102 | cec9845c | Michael Hanselmann | if strict:
|
103 | cec9845c | Michael Hanselmann | tmp = 'yes'
|
104 | cec9845c | Michael Hanselmann | else:
|
105 | cec9845c | Michael Hanselmann | tmp = 'no'
|
106 | cec9845c | Michael Hanselmann | args.append('-oStrictHostKeyChecking=%s' % tmp)
|
107 | cec9845c | Michael Hanselmann | args.append('-oClearAllForwardings=yes')
|
108 | cec9845c | Michael Hanselmann | args.append('-oForwardAgent=yes')
|
109 | cec9845c | Michael Hanselmann | args.append(node) |
110 | cec9845c | Michael Hanselmann | |
111 | cec9845c | Michael Hanselmann | if qa_config.options.dry_run:
|
112 | cec9845c | Michael Hanselmann | prefix = 'exit 0; '
|
113 | cec9845c | Michael Hanselmann | else:
|
114 | cec9845c | Michael Hanselmann | prefix = ''
|
115 | cec9845c | Michael Hanselmann | |
116 | cec9845c | Michael Hanselmann | args.append(prefix + cmd) |
117 | cec9845c | Michael Hanselmann | |
118 | cec9845c | Michael Hanselmann | print 'SSH:', utils.ShellQuoteArgs(args) |
119 | cec9845c | Michael Hanselmann | |
120 | cec9845c | Michael Hanselmann | return args
|
121 | cec9845c | Michael Hanselmann | |
122 | cec9845c | Michael Hanselmann | |
123 | cec9845c | Michael Hanselmann | def StartSSH(node, cmd, strict=True): |
124 | cec9845c | Michael Hanselmann | """Starts SSH.
|
125 | cec9845c | Michael Hanselmann |
|
126 | cec9845c | Michael Hanselmann | """
|
127 | 4b62db14 | Michael Hanselmann | return subprocess.Popen(GetSSHCommand(node, cmd, strict=strict),
|
128 | 4b62db14 | Michael Hanselmann | shell=False)
|
129 | 4b62db14 | Michael Hanselmann | |
130 | 4b62db14 | Michael Hanselmann | |
131 | 4b62db14 | Michael Hanselmann | def GetCommandOutput(node, cmd): |
132 | 4b62db14 | Michael Hanselmann | """Returns the output of a command executed on the given node.
|
133 | 4b62db14 | Michael Hanselmann |
|
134 | 4b62db14 | Michael Hanselmann | """
|
135 | 4b62db14 | Michael Hanselmann | p = subprocess.Popen(GetSSHCommand(node, cmd), |
136 | 4b62db14 | Michael Hanselmann | shell=False, stdout=subprocess.PIPE)
|
137 | 4b62db14 | Michael Hanselmann | AssertEqual(p.wait(), 0)
|
138 | 4b62db14 | Michael Hanselmann | return p.stdout.read()
|
139 | cec9845c | Michael Hanselmann | |
140 | cec9845c | Michael Hanselmann | |
141 | cec9845c | Michael Hanselmann | def UploadFile(node, src): |
142 | cec9845c | Michael Hanselmann | """Uploads a file to a node and returns the filename.
|
143 | cec9845c | Michael Hanselmann |
|
144 | cec9845c | Michael Hanselmann | Caller needs to remove the returned file on the node when it's not needed
|
145 | cec9845c | Michael Hanselmann | anymore.
|
146 | cec9845c | Michael Hanselmann | """
|
147 | cec9845c | Michael Hanselmann | # Make sure nobody else has access to it while preserving local permissions
|
148 | cec9845c | Michael Hanselmann | mode = os.stat(src).st_mode & 0700
|
149 | cec9845c | Michael Hanselmann | |
150 | cec9845c | Michael Hanselmann | cmd = ('tmp=$(tempfile --mode %o --prefix gnt) && '
|
151 | cec9845c | Michael Hanselmann | '[[ -f "${tmp}" ]] && '
|
152 | cec9845c | Michael Hanselmann | 'cat > "${tmp}" && '
|
153 | cec9845c | Michael Hanselmann | 'echo "${tmp}"') % mode
|
154 | cec9845c | Michael Hanselmann | |
155 | cec9845c | Michael Hanselmann | f = open(src, 'r') |
156 | cec9845c | Michael Hanselmann | try:
|
157 | cec9845c | Michael Hanselmann | p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
|
158 | cec9845c | Michael Hanselmann | stdout=subprocess.PIPE) |
159 | cec9845c | Michael Hanselmann | AssertEqual(p.wait(), 0)
|
160 | cec9845c | Michael Hanselmann | |
161 | cec9845c | Michael Hanselmann | # Return temporary filename
|
162 | cec9845c | Michael Hanselmann | return p.stdout.read().strip()
|
163 | cec9845c | Michael Hanselmann | finally:
|
164 | cec9845c | Michael Hanselmann | f.close() |
165 | 5d640672 | Michael Hanselmann | |
166 | 5d640672 | Michael Hanselmann | |
167 | 4b62db14 | Michael Hanselmann | def _ResolveName(cmd, key): |
168 | 4b62db14 | Michael Hanselmann | """Helper function.
|
169 | 4b62db14 | Michael Hanselmann |
|
170 | 4b62db14 | Michael Hanselmann | """
|
171 | 4b62db14 | Michael Hanselmann | master = qa_config.GetMasterNode() |
172 | 4b62db14 | Michael Hanselmann | |
173 | 4b62db14 | Michael Hanselmann | output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
|
174 | 4b62db14 | Michael Hanselmann | for line in output.splitlines(): |
175 | 4b62db14 | Michael Hanselmann | (lkey, lvalue) = line.split(':', 1) |
176 | 4b62db14 | Michael Hanselmann | if lkey == key:
|
177 | 4b62db14 | Michael Hanselmann | return lvalue.lstrip()
|
178 | 4b62db14 | Michael Hanselmann | raise KeyError("Key not found") |
179 | 4b62db14 | Michael Hanselmann | |
180 | 4b62db14 | Michael Hanselmann | |
181 | 5d640672 | Michael Hanselmann | def ResolveInstanceName(instance): |
182 | 5d640672 | Michael Hanselmann | """Gets the full name of an instance.
|
183 | 5d640672 | Michael Hanselmann |
|
184 | 5d640672 | Michael Hanselmann | """
|
185 | e8ae0c20 | Michael Hanselmann | return _ResolveName(['gnt-instance', 'info', instance['name']], |
186 | 4b62db14 | Michael Hanselmann | 'Instance name')
|
187 | 4b62db14 | Michael Hanselmann | |
188 | 4b62db14 | Michael Hanselmann | |
189 | 4b62db14 | Michael Hanselmann | def ResolveNodeName(node): |
190 | 4b62db14 | Michael Hanselmann | """Gets the full name of a node.
|
191 | 4b62db14 | Michael Hanselmann |
|
192 | 4b62db14 | Michael Hanselmann | """
|
193 | 4b62db14 | Michael Hanselmann | return _ResolveName(['gnt-node', 'info', node['primary']], |
194 | 4b62db14 | Michael Hanselmann | 'Node name')
|
195 | 4b62db14 | Michael Hanselmann | |
196 | 4b62db14 | Michael Hanselmann | |
197 | 4b62db14 | Michael Hanselmann | def GetNodeInstances(node, secondaries=False): |
198 | 4b62db14 | Michael Hanselmann | """Gets a list of instances on a node.
|
199 | 4b62db14 | Michael Hanselmann |
|
200 | 4b62db14 | Michael Hanselmann | """
|
201 | 5d640672 | Michael Hanselmann | master = qa_config.GetMasterNode() |
202 | 4b62db14 | Michael Hanselmann | node_name = ResolveNodeName(node) |
203 | 5d640672 | Michael Hanselmann | |
204 | 4b62db14 | Michael Hanselmann | # Get list of all instances
|
205 | 4b62db14 | Michael Hanselmann | cmd = ['gnt-instance', 'list', '--separator=:', '--no-headers', |
206 | 4b62db14 | Michael Hanselmann | '--output=name,pnode,snodes']
|
207 | 4b62db14 | Michael Hanselmann | output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
|
208 | 4b62db14 | Michael Hanselmann | |
209 | 4b62db14 | Michael Hanselmann | instances = [] |
210 | 4b62db14 | Michael Hanselmann | for line in output.splitlines(): |
211 | 4b62db14 | Michael Hanselmann | (name, pnode, snodes) = line.split(':', 2) |
212 | 4b62db14 | Michael Hanselmann | if ((not secondaries and pnode == node_name) or |
213 | 4b62db14 | Michael Hanselmann | (secondaries and node_name in snodes.split(','))): |
214 | 4b62db14 | Michael Hanselmann | instances.append(name) |
215 | 5d640672 | Michael Hanselmann | |
216 | 4b62db14 | Michael Hanselmann | return instances
|
217 | 23269c5b | Michael Hanselmann | |
218 | 23269c5b | Michael Hanselmann | |
219 | dfe11bad | Michael Hanselmann | def _FormatWithColor(text, seq): |
220 | dfe11bad | Michael Hanselmann | if not seq: |
221 | dfe11bad | Michael Hanselmann | return text
|
222 | dfe11bad | Michael Hanselmann | return "%s%s%s" % (seq, text, _RESET_SEQ) |
223 | 23269c5b | Michael Hanselmann | |
224 | 23269c5b | Michael Hanselmann | |
225 | dfe11bad | Michael Hanselmann | FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
|
226 | dfe11bad | Michael Hanselmann | FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
|
227 | dfe11bad | Michael Hanselmann | FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)
|
228 | 1672a0d1 | Michael Hanselmann | |
229 | 1672a0d1 | Michael Hanselmann | |
230 | 1672a0d1 | Michael Hanselmann | def LoadHooks(): |
231 | 1672a0d1 | Michael Hanselmann | """Load all QA hooks.
|
232 | 1672a0d1 | Michael Hanselmann |
|
233 | 1672a0d1 | Michael Hanselmann | """
|
234 | 1672a0d1 | Michael Hanselmann | hooks_dir = qa_config.get('options', {}).get('hooks-dir', None) |
235 | 1672a0d1 | Michael Hanselmann | if not hooks_dir: |
236 | 1672a0d1 | Michael Hanselmann | return
|
237 | 1672a0d1 | Michael Hanselmann | if hooks_dir not in sys.path: |
238 | 1672a0d1 | Michael Hanselmann | sys.path.insert(0, hooks_dir)
|
239 | 1672a0d1 | Michael Hanselmann | for name in utils.ListVisibleFiles(hooks_dir): |
240 | 1672a0d1 | Michael Hanselmann | if name.endswith('.py'): |
241 | 1672a0d1 | Michael Hanselmann | # Load and instanciate hook
|
242 | f9fe750a | Michael Hanselmann | print "Loading hook %s" % name |
243 | 1672a0d1 | Michael Hanselmann | _hooks.append(__import__(name[:-3], None, None, ['']).hook()) |
244 | 1672a0d1 | Michael Hanselmann | |
245 | 1672a0d1 | Michael Hanselmann | |
246 | 1672a0d1 | Michael Hanselmann | class QaHookContext: |
247 | c68d1f43 | Michael Hanselmann | """Definition of context passed to hooks.
|
248 | c68d1f43 | Michael Hanselmann |
|
249 | c68d1f43 | Michael Hanselmann | """
|
250 | 1672a0d1 | Michael Hanselmann | name = None
|
251 | 1672a0d1 | Michael Hanselmann | phase = None
|
252 | 1672a0d1 | Michael Hanselmann | success = None
|
253 | 1672a0d1 | Michael Hanselmann | args = None
|
254 | 1672a0d1 | Michael Hanselmann | kwargs = None
|
255 | 1672a0d1 | Michael Hanselmann | |
256 | 1672a0d1 | Michael Hanselmann | |
257 | 1672a0d1 | Michael Hanselmann | def _CallHooks(ctx): |
258 | 1672a0d1 | Michael Hanselmann | """Calls all hooks with the given context.
|
259 | 1672a0d1 | Michael Hanselmann |
|
260 | 1672a0d1 | Michael Hanselmann | """
|
261 | 1672a0d1 | Michael Hanselmann | if not _hooks: |
262 | 1672a0d1 | Michael Hanselmann | return
|
263 | 1672a0d1 | Michael Hanselmann | |
264 | 1672a0d1 | Michael Hanselmann | name = "%s-%s" % (ctx.phase, ctx.name)
|
265 | 1672a0d1 | Michael Hanselmann | if ctx.success is not None: |
266 | 1672a0d1 | Michael Hanselmann | msg = "%s (success=%s)" % (name, ctx.success)
|
267 | 1672a0d1 | Michael Hanselmann | else:
|
268 | 1672a0d1 | Michael Hanselmann | msg = name |
269 | 1672a0d1 | Michael Hanselmann | print FormatInfo("Begin %s" % msg) |
270 | 1672a0d1 | Michael Hanselmann | for hook in _hooks: |
271 | 1672a0d1 | Michael Hanselmann | hook.run(ctx) |
272 | 1672a0d1 | Michael Hanselmann | print FormatInfo("End %s" % name) |
273 | 1672a0d1 | Michael Hanselmann | |
274 | 1672a0d1 | Michael Hanselmann | |
275 | 1672a0d1 | Michael Hanselmann | def DefineHook(name): |
276 | 1672a0d1 | Michael Hanselmann | """Wraps a function with calls to hooks.
|
277 | 1672a0d1 | Michael Hanselmann |
|
278 | 1672a0d1 | Michael Hanselmann | Usage: prefix function with @qa_utils.DefineHook(...)
|
279 | 1672a0d1 | Michael Hanselmann |
|
280 | c68d1f43 | Michael Hanselmann | This is based on PEP 318, "Decorators for Functions and Methods".
|
281 | abe3db55 | Michael Hanselmann |
|
282 | 1672a0d1 | Michael Hanselmann | """
|
283 | 1672a0d1 | Michael Hanselmann | def wrapper(fn): |
284 | 1672a0d1 | Michael Hanselmann | def new_f(*args, **kwargs): |
285 | 1672a0d1 | Michael Hanselmann | # Create context
|
286 | 1672a0d1 | Michael Hanselmann | ctx = QaHookContext() |
287 | 1672a0d1 | Michael Hanselmann | ctx.name = name |
288 | 1672a0d1 | Michael Hanselmann | ctx.phase = 'pre'
|
289 | 1672a0d1 | Michael Hanselmann | ctx.args = args |
290 | 1672a0d1 | Michael Hanselmann | ctx.kwargs = kwargs |
291 | 1672a0d1 | Michael Hanselmann | |
292 | 1672a0d1 | Michael Hanselmann | _CallHooks(ctx) |
293 | 1672a0d1 | Michael Hanselmann | try:
|
294 | 1672a0d1 | Michael Hanselmann | ctx.phase = 'post'
|
295 | 1672a0d1 | Michael Hanselmann | ctx.success = True
|
296 | 1672a0d1 | Michael Hanselmann | try:
|
297 | 1672a0d1 | Michael Hanselmann | # Call real function
|
298 | 1672a0d1 | Michael Hanselmann | return fn(*args, **kwargs)
|
299 | 1672a0d1 | Michael Hanselmann | except:
|
300 | 1672a0d1 | Michael Hanselmann | ctx.success = False
|
301 | 1672a0d1 | Michael Hanselmann | raise
|
302 | 1672a0d1 | Michael Hanselmann | finally:
|
303 | 1672a0d1 | Michael Hanselmann | _CallHooks(ctx) |
304 | 1672a0d1 | Michael Hanselmann | |
305 | 1672a0d1 | Michael Hanselmann | # Override function metadata
|
306 | 1672a0d1 | Michael Hanselmann | new_f.func_name = fn.func_name |
307 | 1672a0d1 | Michael Hanselmann | new_f.func_doc = fn.func_doc |
308 | 1672a0d1 | Michael Hanselmann | |
309 | 1672a0d1 | Michael Hanselmann | return new_f
|
310 | 1672a0d1 | Michael Hanselmann | |
311 | 1672a0d1 | Michael Hanselmann | return wrapper |