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