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