QA: Remove 'oob_program=default' on gnt-cluster modify
[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   # Find first non master node for tests
236   for node in qa_config.get("nodes"):
237     if node != master:
238       break
239   else:
240     raise qa_error.Error("Can't find non-master node")
241
242   node_name = node["primary"]
243   full_node_name = qa_utils.ResolveNodeName(node)
244
245   (oob_path, verify_path,
246    data_path, exit_code_path) = _CreateOobScriptStructure()
247
248   try:
249     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
250                    "oob_program=%s" % oob_path])
251
252     # No data, exit 0
253     _UpdateOobFile(exit_code_path, "0")
254
255     AssertCommand(["gnt-node", "power", "on", node_name])
256     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
257
258     AssertCommand(["gnt-node", "power", "off", node_name])
259     _AssertOobCall(verify_path, "power-off %s" % full_node_name)
260
261     # Verify we can't transform back to online when not yet powered on
262     AssertCommand(["gnt-node", "modify", "-O", "no", node_name],
263                   fail=True)
264     # Now reset state
265     AssertCommand(["gnt-node", "modify", "-O", "no", "--node-powered", "yes",
266                    node_name])
267
268     AssertCommand(["gnt-node", "power", "cycle", node_name])
269     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
270
271     # This command should fail as it expects output which isn't provided yet
272     # But it should have called the oob helper nevermind
273     AssertCommand(["gnt-node", "power", "status", node_name],
274                   fail=True)
275     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
276
277     # Data, exit 0
278     _UpdateOobFile(data_path, serializer.DumpJson({ "powered": True }))
279
280     AssertCommand(["gnt-node", "power", "status", node_name])
281     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
282
283     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
284     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
285
286     try:
287       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
288       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
289     finally:
290       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
291
292     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
293     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
294
295     # Data, exit 1 (all should fail)
296     _UpdateOobFile(exit_code_path, "1")
297
298     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
299     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
300
301     try:
302       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
303       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
304     finally:
305       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
306
307     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
308     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
309
310     AssertCommand(["gnt-node", "power", "status", node_name],
311                   fail=True)
312     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
313
314     # No data, exit 1 (all should fail)
315     _UpdateOobFile(data_path, "")
316     AssertCommand(["gnt-node", "power", "on", node_name], fail=True)
317     _AssertOobCall(verify_path, "power-on %s" % full_node_name)
318
319     try:
320       AssertCommand(["gnt-node", "power", "off", node_name], fail=True)
321       _AssertOobCall(verify_path, "power-off %s" % full_node_name)
322     finally:
323       AssertCommand(["gnt-node", "modify", "-O", "no", node_name])
324
325     AssertCommand(["gnt-node", "power", "cycle", node_name], fail=True)
326     _AssertOobCall(verify_path, "power-cycle %s" % full_node_name)
327
328     AssertCommand(["gnt-node", "power", "status", node_name],
329                   fail=True)
330     _AssertOobCall(verify_path, "power-status %s" % full_node_name)
331
332     # Different OOB script for node
333     verify_path2 = qa_utils.UploadData(master["primary"], "")
334     oob_script = ("#!/bin/sh\n"
335                   "echo \"$@\" > %s\n") % verify_path2
336     oob_path2 = qa_utils.UploadData(master["primary"], oob_script, mode=0700)
337
338     try:
339       AssertCommand(["gnt-node", "modify", "--node-parameters",
340                      "oob_program=%s" % oob_path2, node_name])
341       AssertCommand(["gnt-node", "power", "on", node_name])
342       _AssertOobCall(verify_path2, "power-on %s" % full_node_name)
343     finally:
344       AssertCommand(["gnt-node", "modify", "--node-parameters",
345                      "oob_program=default", node_name])
346       AssertCommand(["rm", "-f", oob_path2, verify_path2])
347   finally:
348     AssertCommand(["gnt-cluster", "modify", "--node-parameters",
349                    "oob_program="])
350     AssertCommand(["rm", "-f", oob_path, verify_path, data_path,
351                    exit_code_path])
352
353
354 def TestNodeList():
355   """gnt-node list"""
356   qa_utils.GenericQueryTest("gnt-node", query.NODE_FIELDS.keys())
357
358
359 def TestNodeListFields():
360   """gnt-node list-fields"""
361   qa_utils.GenericQueryFieldsTest("gnt-node", query.NODE_FIELDS.keys())