Fix bug in “gnt-node list-storage”
[ganeti-local] / qa / qa_rapi.py
1 #
2
3 # Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19
20
21 """Remote API QA tests.
22
23 """
24
25 import tempfile
26
27 from ganeti import utils
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import serializer
31 from ganeti import cli
32 from ganeti import rapi
33
34 import ganeti.rapi.client
35 import ganeti.rapi.client_utils
36
37 import qa_config
38 import qa_utils
39 import qa_error
40
41 from qa_utils import (AssertEqual, AssertNotEqual, AssertIn, AssertMatch,
42                       StartLocalCommand)
43
44
45 _rapi_ca = None
46 _rapi_client = None
47 _rapi_username = None
48 _rapi_password = None
49
50
51 def Setup(username, password):
52   """Configures the RAPI client.
53
54   """
55   global _rapi_ca
56   global _rapi_client
57   global _rapi_username
58   global _rapi_password
59
60   _rapi_username = username
61   _rapi_password = password
62
63   master = qa_config.GetMasterNode()
64
65   # Load RAPI certificate from master node
66   cmd = ["cat", constants.RAPI_CERT_FILE]
67
68   # Write to temporary file
69   _rapi_ca = tempfile.NamedTemporaryFile()
70   _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
71                                            utils.ShellQuoteArgs(cmd)))
72   _rapi_ca.flush()
73
74   port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
75   cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
76                                            proxy="")
77
78   _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
79                                               username=username,
80                                               password=password,
81                                               curl_config_fn=cfg_curl)
82
83   print "RAPI protocol version: %s" % _rapi_client.GetVersion()
84
85
86 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
87                    "admin_state",
88                    "disk_template", "disk.sizes",
89                    "nic.ips", "nic.macs", "nic.modes", "nic.links",
90                    "beparams", "hvparams",
91                    "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
92
93 NODE_FIELDS = ("name", "dtotal", "dfree",
94                "mtotal", "mnode", "mfree",
95                "pinst_cnt", "sinst_cnt", "tags")
96
97 JOB_FIELDS = frozenset([
98   "id", "ops", "status", "summary",
99   "opstatus", "opresult", "oplog",
100   "received_ts", "start_ts", "end_ts",
101   ])
102
103 LIST_FIELDS = ("id", "uri")
104
105
106 def Enabled():
107   """Return whether remote API tests should be run.
108
109   """
110   return qa_config.TestEnabled('rapi')
111
112
113 def _DoTests(uris):
114   results = []
115
116   for uri, verify, method, body in uris:
117     assert uri.startswith("/")
118
119     print "%s %s" % (method, uri)
120     data = _rapi_client._SendRequest(method, uri, None, body)
121
122     if verify is not None:
123       if callable(verify):
124         verify(data)
125       else:
126         AssertEqual(data, verify)
127
128     results.append(data)
129
130   return results
131
132
133 def _VerifyReturnsJob(data):
134   AssertMatch(data, r'^\d+$')
135
136
137 def TestVersion():
138   """Testing remote API version.
139
140   """
141   _DoTests([
142     ("/version", constants.RAPI_VERSION, 'GET', None),
143     ])
144
145
146 def TestEmptyCluster():
147   """Testing remote API on an empty cluster.
148
149   """
150   master = qa_config.GetMasterNode()
151   master_full = qa_utils.ResolveNodeName(master)
152
153   def _VerifyInfo(data):
154     AssertIn("name", data)
155     AssertIn("master", data)
156     AssertEqual(data["master"], master_full)
157
158   def _VerifyNodes(data):
159     master_entry = {
160       "id": master_full,
161       "uri": "/2/nodes/%s" % master_full,
162       }
163     AssertIn(master_entry, data)
164
165   def _VerifyNodesBulk(data):
166     for node in data:
167       for entry in NODE_FIELDS:
168         AssertIn(entry, node)
169
170   _DoTests([
171     ("/", None, 'GET', None),
172     ("/2/info", _VerifyInfo, 'GET', None),
173     ("/2/tags", None, 'GET', None),
174     ("/2/nodes", _VerifyNodes, 'GET', None),
175     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
176     ("/2/instances", [], 'GET', None),
177     ("/2/instances?bulk=1", [], 'GET', None),
178     ("/2/os", None, 'GET', None),
179     ])
180
181   # Test HTTP Not Found
182   for method in ["GET", "PUT", "POST", "DELETE"]:
183     try:
184       _DoTests([("/99/resource/not/here/99", None, method, None)])
185     except rapi.client.GanetiApiError, err:
186       AssertEqual(err.code, 404)
187     else:
188       raise qa_error.Error("Non-existent resource didn't return HTTP 404")
189
190   # Test HTTP Not Implemented
191   for method in ["PUT", "POST", "DELETE"]:
192     try:
193       _DoTests([("/version", None, method, None)])
194     except rapi.client.GanetiApiError, err:
195       AssertEqual(err.code, 501)
196     else:
197       raise qa_error.Error("Non-implemented method didn't fail")
198
199
200 def TestInstance(instance):
201   """Testing getting instance(s) info via remote API.
202
203   """
204   def _VerifyInstance(data):
205     for entry in INSTANCE_FIELDS:
206       AssertIn(entry, data)
207
208   def _VerifyInstancesList(data):
209     for instance in data:
210       for entry in LIST_FIELDS:
211         AssertIn(entry, instance)
212
213   def _VerifyInstancesBulk(data):
214     for instance_data in data:
215       _VerifyInstance(instance_data)
216
217   _DoTests([
218     ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
219     ("/2/instances", _VerifyInstancesList, 'GET', None),
220     ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
221     ("/2/instances/%s/activate-disks" % instance["name"],
222      _VerifyReturnsJob, 'PUT', None),
223     ("/2/instances/%s/deactivate-disks" % instance["name"],
224      _VerifyReturnsJob, 'PUT', None),
225     ])
226
227   # Test OpPrepareExport
228   (job_id, ) = _DoTests([
229     ("/2/instances/%s/prepare-export?mode=%s" %
230      (instance["name"], constants.EXPORT_MODE_REMOTE),
231      _VerifyReturnsJob, "PUT", None),
232     ])
233
234   result = _WaitForRapiJob(job_id)[0]
235   AssertEqual(len(result["handshake"]), 3)
236   AssertEqual(result["handshake"][0], constants.RIE_VERSION)
237   AssertEqual(len(result["x509_key_name"]), 3)
238   AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
239
240
241 def TestNode(node):
242   """Testing getting node(s) info via remote API.
243
244   """
245   def _VerifyNode(data):
246     for entry in NODE_FIELDS:
247       AssertIn(entry, data)
248
249   def _VerifyNodesList(data):
250     for node in data:
251       for entry in LIST_FIELDS:
252         AssertIn(entry, node)
253
254   def _VerifyNodesBulk(data):
255     for node_data in data:
256       _VerifyNode(node_data)
257
258   _DoTests([
259     ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
260     ("/2/nodes", _VerifyNodesList, 'GET', None),
261     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
262     ])
263
264
265 def TestTags(kind, name, tags):
266   """Tests .../tags resources.
267
268   """
269   if kind == constants.TAG_CLUSTER:
270     uri = "/2/tags"
271   elif kind == constants.TAG_NODE:
272     uri = "/2/nodes/%s/tags" % name
273   elif kind == constants.TAG_INSTANCE:
274     uri = "/2/instances/%s/tags" % name
275   else:
276     raise errors.ProgrammerError("Unknown tag kind")
277
278   def _VerifyTags(data):
279     AssertEqual(sorted(tags), sorted(data))
280
281   query = "&".join("tag=%s" % i for i in tags)
282
283   # Add tags
284   (job_id, ) = _DoTests([
285     ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
286     ])
287   _WaitForRapiJob(job_id)
288
289   # Retrieve tags
290   _DoTests([
291     (uri, _VerifyTags, 'GET', None),
292     ])
293
294   # Remove tags
295   (job_id, ) = _DoTests([
296     ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
297     ])
298   _WaitForRapiJob(job_id)
299
300
301 def _WaitForRapiJob(job_id):
302   """Waits for a job to finish.
303
304   """
305   master = qa_config.GetMasterNode()
306
307   def _VerifyJob(data):
308     AssertEqual(data["id"], job_id)
309     for field in JOB_FIELDS:
310       AssertIn(field, data)
311
312   _DoTests([
313     ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
314     ])
315
316   return rapi.client_utils.PollJob(_rapi_client, job_id,
317                                    cli.StdioJobPollReportCb())
318
319
320 def TestRapiInstanceAdd(node, use_client):
321   """Test adding a new instance via RAPI"""
322   instance = qa_config.AcquireInstance()
323   try:
324     memory = utils.ParseUnit(qa_config.get("mem"))
325     disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
326
327     if use_client:
328       disks = [{"size": size} for size in disk_sizes]
329       nics = [{}]
330
331       beparams = {
332         constants.BE_MEMORY: memory,
333         }
334
335       job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
336                                            instance["name"],
337                                            constants.DT_PLAIN,
338                                            disks, nics,
339                                            os=qa_config.get("os"),
340                                            pnode=node["primary"],
341                                            beparams=beparams)
342     else:
343       body = {
344         "name": instance["name"],
345         "os": qa_config.get("os"),
346         "disk_template": constants.DT_PLAIN,
347         "pnode": node["primary"],
348         "memory": memory,
349         "disks": disk_sizes,
350         }
351
352       (job_id, ) = _DoTests([
353         ("/2/instances", _VerifyReturnsJob, "POST", body),
354         ])
355
356     _WaitForRapiJob(job_id)
357
358     return instance
359   except:
360     qa_config.ReleaseInstance(instance)
361     raise
362
363
364 def TestRapiInstanceRemove(instance, use_client):
365   """Test removing instance via RAPI"""
366   if use_client:
367     job_id = _rapi_client.DeleteInstance(instance["name"])
368   else:
369     (job_id, ) = _DoTests([
370       ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
371       ])
372
373   _WaitForRapiJob(job_id)
374
375   qa_config.ReleaseInstance(instance)
376
377
378 def TestRapiInstanceMigrate(instance):
379   """Test migrating instance via RAPI"""
380   # Move to secondary node
381   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
382   # And back to previous primary
383   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
384
385
386 def TestRapiInstanceRename(instance, rename_target):
387   """Test renaming instance via RAPI"""
388   rename_source = instance["name"]
389
390   for name1, name2 in [(rename_source, rename_target),
391                        (rename_target, rename_source)]:
392     _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
393
394
395 def TestRapiInstanceModify(instance):
396   """Test modifying instance via RAPI"""
397   def _ModifyInstance(**kwargs):
398     _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
399
400   _ModifyInstance(hvparams={
401     constants.HV_KERNEL_ARGS: "single",
402     })
403
404   _ModifyInstance(beparams={
405     constants.BE_VCPUS: 3,
406     })
407
408   _ModifyInstance(beparams={
409     constants.BE_VCPUS: constants.VALUE_DEFAULT,
410     })
411
412   _ModifyInstance(hvparams={
413     constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
414     })
415
416
417 def TestInterClusterInstanceMove(src_instance, dest_instance,
418                                  pnode, snode, tnode):
419   """Test tools/move-instance"""
420   master = qa_config.GetMasterNode()
421
422   rapi_pw_file = tempfile.NamedTemporaryFile()
423   rapi_pw_file.write(_rapi_password)
424   rapi_pw_file.flush()
425
426   # TODO: Run some instance tests before moving back
427
428   if snode is None:
429     # instance is not redundant, but we still need to pass a node
430     # (which will be ignored)
431     fsec = tnode
432   else:
433     fsec = snode
434   # note: pnode:snode are the *current* nodes, so we move it first to
435   # tnode:pnode, then back to pnode:snode
436   for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
437                           tnode["primary"], pnode["primary"]),
438                          (dest_instance["name"], src_instance["name"],
439                           pnode["primary"], fsec["primary"])]:
440     cmd = [
441       "../tools/move-instance",
442       "--verbose",
443       "--src-ca-file=%s" % _rapi_ca.name,
444       "--src-username=%s" % _rapi_username,
445       "--src-password-file=%s" % rapi_pw_file.name,
446       "--dest-instance-name=%s" % di,
447       "--dest-primary-node=%s" % pn,
448       "--dest-secondary-node=%s" % sn,
449       "--net=0:mac=%s" % constants.VALUE_GENERATE,
450       master["primary"],
451       master["primary"],
452       si,
453       ]
454
455     AssertEqual(StartLocalCommand(cmd).wait(), 0)