Run pylint over QA code too
[ganeti-local] / qa / qa_node.py
1 #
2 #
3
4 # Copyright (C) 2007, 2011 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 """Node-related QA tests.
23
24 """
25
26 from ganeti import utils
27 from ganeti import constants
28 from ganeti import query
29 from ganeti import serializer
30
31 import qa_config
32 import qa_error
33 import qa_utils
34
35 from qa_utils import AssertCommand
36
37
38 def _NodeAdd(node, readd=False):
39   if not readd and node.get('_added', False):
40     raise qa_error.Error("Node %s already in cluster" % node['primary'])
41   elif readd and not node.get('_added', False):
42     raise qa_error.Error("Node %s not yet in cluster" % node['primary'])
43
44   cmd = ['gnt-node', 'add', "--no-ssh-key-check"]
45   if node.get('secondary', None):
46     cmd.append('--secondary-ip=%s' % node['secondary'])
47   if readd:
48     cmd.append('--readd')
49   cmd.append(node['primary'])
50
51   AssertCommand(cmd)
52
53   node['_added'] = True
54
55
56 def _NodeRemove(node):
57   AssertCommand(["gnt-node", "remove", node["primary"]])
58   node['_added'] = False
59
60
61 def TestNodeAddAll():
62   """Adding all nodes to cluster."""
63   master = qa_config.GetMasterNode()
64   for node in qa_config.get('nodes'):
65     if node != master:
66       _NodeAdd(node, readd=False)
67
68
69 def MarkNodeAddedAll():
70   """Mark all nodes as added.
71
72   This is useful if we don't create the cluster ourselves (in qa).
73
74   """
75   master = qa_config.GetMasterNode()
76   for node in qa_config.get('nodes'):
77     if node != master:
78       node['_added'] = True
79
80
81 def TestNodeRemoveAll():
82   """Removing all nodes from cluster."""
83   master = qa_config.GetMasterNode()
84   for node in qa_config.get('nodes'):
85     if node != master:
86       _NodeRemove(node)
87
88
89 def TestNodeReadd(node):
90   """gnt-node add --readd"""
91   _NodeAdd(node, readd=True)
92
93
94 def TestNodeInfo():
95   """gnt-node info"""
96   AssertCommand(["gnt-node", "info"])
97
98
99 def TestNodeVolumes():
100   """gnt-node volumes"""
101   AssertCommand(["gnt-node", "volumes"])
102
103
104 def TestNodeStorage():
105   """gnt-node storage"""
106   master = qa_config.GetMasterNode()
107
108   for storage_type in constants.VALID_STORAGE_TYPES:
109     # Test simple list
110     AssertCommand(["gnt-node", "list-storage", "--storage-type", storage_type])
111
112     # Test all storage fields
113     cmd = ["gnt-node", "list-storage", "--storage-type", storage_type,
114            "--output=%s" % ",".join(list(constants.VALID_STORAGE_FIELDS) +
115                                     [constants.SF_NODE, constants.SF_TYPE])]
116     AssertCommand(cmd)
117
118     # Get list of valid storage devices
119     cmd = ["gnt-node", "list-storage", "--storage-type", storage_type,
120            "--output=node,name,allocatable", "--separator=|",
121            "--no-headers"]
122     output = qa_utils.GetCommandOutput(master["primary"],
123                                        utils.ShellQuoteArgs(cmd))
124
125     # Test with up to two devices
126     testdevcount = 2
127
128     for line in output.splitlines()[:testdevcount]:
129       (node_name, st_name, st_allocatable) = line.split("|")
130
131       # Dummy modification without any changes
132       cmd = ["gnt-node", "modify-storage", node_name, storage_type, st_name]
133       AssertCommand(cmd)
134
135       # Make sure we end up with the same value as before
136       if st_allocatable.lower() == "y":
137         test_allocatable = ["no", "yes"]
138       else:
139         test_allocatable = ["yes", "no"]
140
141       fail = (constants.SF_ALLOCATABLE not in
142               constants.MODIFIABLE_STORAGE_FIELDS.get(storage_type, []))
143
144       for i in test_allocatable:
145         AssertCommand(["gnt-node", "modify-storage", "--allocatable", i,
146                        node_name, storage_type, st_name], fail=fail)
147
148       # Test repair functionality
149       fail = (constants.SO_FIX_CONSISTENCY not in
150               constants.VALID_STORAGE_OPERATIONS.get(storage_type, []))
151       AssertCommand(["gnt-node", "repair-storage", node_name,
152                      storage_type, st_name], fail=fail)
153
154
155 def TestNodeFailover(node, node2):
156   """gnt-node failover"""
157   if qa_utils.GetNodeInstances(node2, secondaries=False):
158     raise qa_error.UnusableNodeError("Secondary node has at least one"
159                                      " primary instance. This test requires"
160                                      " it to have no primary instances.")
161
162   # Fail over to secondary node
163   AssertCommand(["gnt-node", "failover", "-f", node["primary"]])
164
165   # ... and back again.
166   AssertCommand(["gnt-node", "failover", "-f", node2["primary"]])
167
168
169 def TestNodeEvacuate(node, node2):
170   """gnt-node evacuate"""
171   node3 = qa_config.AcquireNode(exclude=[node, node2])
172   try:
173     if qa_utils.GetNodeInstances(node3, secondaries=True):
174       raise qa_error.UnusableNodeError("Evacuation node has at least one"
175                                        " secondary instance. This test requires"
176                                        " it to have no secondary instances.")
177
178     # Evacuate all secondary instances
179     AssertCommand(["gnt-node", "evacuate", "-f",
180                    "--new-secondary=%s" % node3["primary"], node2["primary"]])
181
182     # ... and back again.
183     AssertCommand(["gnt-node", "evacuate", "-f",
184                    "--new-secondary=%s" % node2["primary"], node3["primary"]])
185   finally:
186     qa_config.ReleaseNode(node3)
187
188
189 def TestNodeModify(node):
190   """gnt-node modify"""
191   for flag in ["master-candidate", "drained", "offline"]:
192     for value in ["yes", "no"]:
193       AssertCommand(["gnt-node", "modify", "--force",
194                      "--%s=%s" % (flag, value), node["primary"]])
195
196   AssertCommand(["gnt-node", "modify", "--master-candidate=yes",
197                  "--auto-promote", node["primary"]])
198
199
200 def _CreateOobScriptStructure():
201   """Create a simple OOB handling script and its structure."""
202   master = qa_config.GetMasterNode()
203
204   data_path = qa_utils.UploadData(master["primary"], "")
205   verify_path = qa_utils.UploadData(master["primary"], "")
206   exit_code_path = qa_utils.UploadData(master["primary"], "")
207
208   oob_script = (("#!/bin/bash\n"
209                  "echo \"$@\" > %s\n"
210                  "cat %s\n"
211                  "exit $(< %s)\n") %
212                 (utils.ShellQuote(verify_path), utils.ShellQuote(data_path),
213                  utils.ShellQuote(exit_code_path)))
214   oob_path = qa_utils.UploadData(master["primary"], oob_script, mode=0700)
215
216   return [oob_path, verify_path, data_path, exit_code_path]
217
218
219 def _UpdateOobFile(path, data):
220   """Updates the data file with data."""
221   master = qa_config.GetMasterNode()
222   qa_utils.UploadData(master["primary"], data, filename=path)
223
224
225 def _AssertOobCall(verify_path, expected_args):
226   """Assert the OOB call was performed with expetected args."""
227   master = qa_config.GetMasterNode()
228
229   verify_output_cmd = utils.ShellQuoteArgs(["cat", verify_path])
230   output = qa_utils.GetCommandOutput(master["primary"], verify_output_cmd)
231
232   qa_utils.AssertEqual(expected_args, output.strip())
233
234
235 def TestOutOfBand():
236   """gnt-node power"""
237   master = qa_config.GetMasterNode()
238
239   node = qa_config.AcquireNode(exclude=master)
240
241   node_name = node["primary"]
242   full_node_name = qa_utils.ResolveNodeName(node)
243
244   (oob_path, verify_path,
245    data_path, exit_code_path) = _CreateOobScriptStructure()
246
247   try:
248     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
249                    "oob_program=%s" % oob_path])
250
251     # No data, exit 0
252     _UpdateOobFile(exit_code_path, "0")
253
254     AssertCommand(["gnt-node", "power", "on", node_name])
255     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
256
257     AssertCommand(["gnt-node", "power", "off", node_name])
258     _AssertOobCall(verify_path, "power-off %s" % full_node_name)
259
260     # Verify we can't transform back to online when not yet powered on
261     AssertCommand(["gnt-node", "modify", "-O", "no", node_name],
262                   fail=True)
263     # Now reset state
264     AssertCommand(["gnt-node", "modify", "-O", "no", "--node-powered", "yes",
265                    node_name])
266
267     AssertCommand(["gnt-node", "power", "cycle", node_name])
268     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
269
270     # This command should fail as it expects output which isn't provided yet
271     # But it should have called the oob helper nevermind
272     AssertCommand(["gnt-node", "power", "status", node_name],
273                   fail=True)
274     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
275
276     # Data, exit 0
277     _UpdateOobFile(data_path, serializer.DumpJson({ "powered": True }))
278
279     AssertCommand(["gnt-node", "power", "status", node_name])
280     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
281
282     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
283     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
284
285     try:
286       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
287       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
288     finally:
289       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
290
291     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
292     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
293
294     # Data, exit 1 (all should fail)
295     _UpdateOobFile(exit_code_path, "1")
296
297     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
298     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
299
300     try:
301       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
302       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
303     finally:
304       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
305
306     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
307     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
308
309     AssertCommand(["gnt-node", "power", "status", node_name],
310                   fail=True)
311     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
312
313     # No data, exit 1 (all should fail)
314     _UpdateOobFile(data_path, "")
315     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
316     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
317
318     try:
319       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
320       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
321     finally:
322       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
323
324     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
325     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
326
327     AssertCommand(["gnt-node", "power", "status", node_name],
328                   fail=True)
329     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
330
331     # Different OOB script for node
332     verify_path2 = qa_utils.UploadData(master["primary"], "")
333     oob_script = ("#!/bin/sh\n"
334                   "echo \"$@\" > %s\n") % verify_path2
335     oob_path2 = qa_utils.UploadData(master["primary"], oob_script, mode=0700)
336
337     try:
338       AssertCommand(["gnt-node", "modify", "--node-parameters",
339                      "oob_program=%s" % oob_path2, node_name])
340       AssertCommand(["gnt-node", "power", "on", node_name])
341       _AssertOobCall(verify_path2, "power-on %s" % full_node_name)
342     finally:
343       AssertCommand(["gnt-node", "modify", "--node-parameters",
344                      "oob_program=default", node_name])
345       AssertCommand(["rm", "-f", oob_path2, verify_path2])
346   finally:
347     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
348                    "oob_program="])
349     AssertCommand(["rm", "-f", oob_path, verify_path, data_path,
350                    exit_code_path])
351
352
353 def TestNodeList():
354   """gnt-node list"""
355   qa_utils.GenericQueryTest("gnt-node", query.NODE_FIELDS.keys())
356
357
358 def TestNodeListFields():
359   """gnt-node list-fields"""
360   qa_utils.GenericQueryFieldsTest("gnt-node", query.NODE_FIELDS.keys())