Modify gnt-node add to call external script
[ganeti-local] / qa / qa_utils.py
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 GetSSHCommand(node, cmd, strict=True):
105   """Builds SSH command to be executed.
106
107   Args:
108   - node: Node the command should run on
109   - cmd: Command to be executed as a list with all parameters
110   - strict: Whether to enable strict host key checking
111
112   """
113   args = [ 'ssh', '-oEscapeChar=none', '-oBatchMode=yes', '-l', 'root', '-t' ]
114
115   if strict:
116     tmp = 'yes'
117   else:
118     tmp = 'no'
119   args.append('-oStrictHostKeyChecking=%s' % tmp)
120   args.append('-oClearAllForwardings=yes')
121   args.append('-oForwardAgent=yes')
122   args.append(node)
123   args.append(cmd)
124
125   return args
126
127
128 def StartLocalCommand(cmd, **kwargs):
129   """Starts a local command.
130
131   """
132   print "Command: %s" % utils.ShellQuoteArgs(cmd)
133   return subprocess.Popen(cmd, shell=False, **kwargs)
134
135
136 def StartSSH(node, cmd, strict=True):
137   """Starts SSH.
138
139   """
140   return StartLocalCommand(GetSSHCommand(node, cmd, strict=strict))
141
142
143 def GetCommandOutput(node, cmd):
144   """Returns the output of a command executed on the given node.
145
146   """
147   p = StartLocalCommand(GetSSHCommand(node, cmd), stdout=subprocess.PIPE)
148   AssertEqual(p.wait(), 0)
149   return p.stdout.read()
150
151
152 def UploadFile(node, src):
153   """Uploads a file to a node and returns the filename.
154
155   Caller needs to remove the returned file on the node when it's not needed
156   anymore.
157
158   """
159   # Make sure nobody else has access to it while preserving local permissions
160   mode = os.stat(src).st_mode & 0700
161
162   cmd = ('tmp=$(tempfile --mode %o --prefix gnt) && '
163          '[[ -f "${tmp}" ]] && '
164          'cat > "${tmp}" && '
165          'echo "${tmp}"') % mode
166
167   f = open(src, 'r')
168   try:
169     p = subprocess.Popen(GetSSHCommand(node, cmd), shell=False, stdin=f,
170                          stdout=subprocess.PIPE)
171     AssertEqual(p.wait(), 0)
172
173     # Return temporary filename
174     return p.stdout.read().strip()
175   finally:
176     f.close()
177
178
179 def BackupFile(node, path):
180   """Creates a backup of a file on the node and returns the filename.
181
182   Caller needs to remove the returned file on the node when it's not needed
183   anymore.
184
185   """
186   cmd = ("tmp=$(tempfile --prefix .gnt --directory=$(dirname %s)) && "
187          "[[ -f \"$tmp\" ]] && "
188          "cp %s $tmp && "
189          "echo $tmp") % (utils.ShellQuote(path), utils.ShellQuote(path))
190
191   # Return temporary filename
192   return GetCommandOutput(node, cmd).strip()
193
194
195 def _ResolveName(cmd, key):
196   """Helper function.
197
198   """
199   master = qa_config.GetMasterNode()
200
201   output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
202   for line in output.splitlines():
203     (lkey, lvalue) = line.split(':', 1)
204     if lkey == key:
205       return lvalue.lstrip()
206   raise KeyError("Key not found")
207
208
209 def ResolveInstanceName(instance):
210   """Gets the full name of an instance.
211
212   """
213   return _ResolveName(['gnt-instance', 'info', instance['name']],
214                       'Instance name')
215
216
217 def ResolveNodeName(node):
218   """Gets the full name of a node.
219
220   """
221   return _ResolveName(['gnt-node', 'info', node['primary']],
222                       'Node name')
223
224
225 def GetNodeInstances(node, secondaries=False):
226   """Gets a list of instances on a node.
227
228   """
229   master = qa_config.GetMasterNode()
230   node_name = ResolveNodeName(node)
231
232   # Get list of all instances
233   cmd = ['gnt-instance', 'list', '--separator=:', '--no-headers',
234          '--output=name,pnode,snodes']
235   output = GetCommandOutput(master['primary'], utils.ShellQuoteArgs(cmd))
236
237   instances = []
238   for line in output.splitlines():
239     (name, pnode, snodes) = line.split(':', 2)
240     if ((not secondaries and pnode == node_name) or
241         (secondaries and node_name in snodes.split(','))):
242       instances.append(name)
243
244   return instances
245
246
247 def _FormatWithColor(text, seq):
248   if not seq:
249     return text
250   return "%s%s%s" % (seq, text, _RESET_SEQ)
251
252
253 FormatWarning = lambda text: _FormatWithColor(text, _WARNING_SEQ)
254 FormatError = lambda text: _FormatWithColor(text, _ERROR_SEQ)
255 FormatInfo = lambda text: _FormatWithColor(text, _INFO_SEQ)