Simplify instance rename qa test
[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 GROUP_FIELDS = frozenset([
98   "name", "uuid",
99   "node_cnt", "node_list",
100   ])
101
102 JOB_FIELDS = frozenset([
103   "id", "ops", "status", "summary",
104   "opstatus", "opresult", "oplog",
105   "received_ts", "start_ts", "end_ts",
106   ])
107
108 LIST_FIELDS = ("id", "uri")
109
110
111 def Enabled():
112   """Return whether remote API tests should be run.
113
114   """
115   return qa_config.TestEnabled('rapi')
116
117
118 def _DoTests(uris):
119   results = []
120
121   for uri, verify, method, body in uris:
122     assert uri.startswith("/")
123
124     print "%s %s" % (method, uri)
125     data = _rapi_client._SendRequest(method, uri, None, body)
126
127     if verify is not None:
128       if callable(verify):
129         verify(data)
130       else:
131         AssertEqual(data, verify)
132
133     results.append(data)
134
135   return results
136
137
138 def _VerifyReturnsJob(data):
139   AssertMatch(data, r'^\d+$')
140
141
142 def TestVersion():
143   """Testing remote API version.
144
145   """
146   _DoTests([
147     ("/version", constants.RAPI_VERSION, 'GET', None),
148     ])
149
150
151 def TestEmptyCluster():
152   """Testing remote API on an empty cluster.
153
154   """
155   master = qa_config.GetMasterNode()
156   master_full = qa_utils.ResolveNodeName(master)
157
158   def _VerifyInfo(data):
159     AssertIn("name", data)
160     AssertIn("master", data)
161     AssertEqual(data["master"], master_full)
162
163   def _VerifyNodes(data):
164     master_entry = {
165       "id": master_full,
166       "uri": "/2/nodes/%s" % master_full,
167       }
168     AssertIn(master_entry, data)
169
170   def _VerifyNodesBulk(data):
171     for node in data:
172       for entry in NODE_FIELDS:
173         AssertIn(entry, node)
174
175   def _VerifyGroups(data):
176     default_group = {
177       "name": constants.INITIAL_NODE_GROUP_NAME,
178       "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
179       }
180     AssertIn(default_group, data)
181
182   def _VerifyGroupsBulk(data):
183     for group in data:
184       for field in GROUP_FIELDS:
185         AssertIn(field, group)
186
187   _DoTests([
188     ("/", None, 'GET', None),
189     ("/2/info", _VerifyInfo, 'GET', None),
190     ("/2/tags", None, 'GET', None),
191     ("/2/nodes", _VerifyNodes, 'GET', None),
192     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
193     ("/2/groups", _VerifyGroups, 'GET', None),
194     ("/2/groups?bulk=1", _VerifyGroupsBulk, 'GET', None),
195     ("/2/instances", [], 'GET', None),
196     ("/2/instances?bulk=1", [], 'GET', None),
197     ("/2/os", None, 'GET', None),
198     ])
199
200   # Test HTTP Not Found
201   for method in ["GET", "PUT", "POST", "DELETE"]:
202     try:
203       _DoTests([("/99/resource/not/here/99", None, method, None)])
204     except rapi.client.GanetiApiError, err:
205       AssertEqual(err.code, 404)
206     else:
207       raise qa_error.Error("Non-existent resource didn't return HTTP 404")
208
209   # Test HTTP Not Implemented
210   for method in ["PUT", "POST", "DELETE"]:
211     try:
212       _DoTests([("/version", None, method, None)])
213     except rapi.client.GanetiApiError, err:
214       AssertEqual(err.code, 501)
215     else:
216       raise qa_error.Error("Non-implemented method didn't fail")
217
218
219 def TestInstance(instance):
220   """Testing getting instance(s) info via remote API.
221
222   """
223   def _VerifyInstance(data):
224     for entry in INSTANCE_FIELDS:
225       AssertIn(entry, data)
226
227   def _VerifyInstancesList(data):
228     for instance in data:
229       for entry in LIST_FIELDS:
230         AssertIn(entry, instance)
231
232   def _VerifyInstancesBulk(data):
233     for instance_data in data:
234       _VerifyInstance(instance_data)
235
236   _DoTests([
237     ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
238     ("/2/instances", _VerifyInstancesList, 'GET', None),
239     ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
240     ("/2/instances/%s/activate-disks" % instance["name"],
241      _VerifyReturnsJob, 'PUT', None),
242     ("/2/instances/%s/deactivate-disks" % instance["name"],
243      _VerifyReturnsJob, 'PUT', None),
244     ])
245
246   # Test OpPrepareExport
247   (job_id, ) = _DoTests([
248     ("/2/instances/%s/prepare-export?mode=%s" %
249      (instance["name"], constants.EXPORT_MODE_REMOTE),
250      _VerifyReturnsJob, "PUT", None),
251     ])
252
253   result = _WaitForRapiJob(job_id)[0]
254   AssertEqual(len(result["handshake"]), 3)
255   AssertEqual(result["handshake"][0], constants.RIE_VERSION)
256   AssertEqual(len(result["x509_key_name"]), 3)
257   AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
258
259
260 def TestNode(node):
261   """Testing getting node(s) info via remote API.
262
263   """
264   def _VerifyNode(data):
265     for entry in NODE_FIELDS:
266       AssertIn(entry, data)
267
268   def _VerifyNodesList(data):
269     for node in data:
270       for entry in LIST_FIELDS:
271         AssertIn(entry, node)
272
273   def _VerifyNodesBulk(data):
274     for node_data in data:
275       _VerifyNode(node_data)
276
277   _DoTests([
278     ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
279     ("/2/nodes", _VerifyNodesList, 'GET', None),
280     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
281     ])
282
283
284 def TestTags(kind, name, tags):
285   """Tests .../tags resources.
286
287   """
288   if kind == constants.TAG_CLUSTER:
289     uri = "/2/tags"
290   elif kind == constants.TAG_NODE:
291     uri = "/2/nodes/%s/tags" % name
292   elif kind == constants.TAG_INSTANCE:
293     uri = "/2/instances/%s/tags" % name
294   else:
295     raise errors.ProgrammerError("Unknown tag kind")
296
297   def _VerifyTags(data):
298     AssertEqual(sorted(tags), sorted(data))
299
300   query = "&".join("tag=%s" % i for i in tags)
301
302   # Add tags
303   (job_id, ) = _DoTests([
304     ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
305     ])
306   _WaitForRapiJob(job_id)
307
308   # Retrieve tags
309   _DoTests([
310     (uri, _VerifyTags, 'GET', None),
311     ])
312
313   # Remove tags
314   (job_id, ) = _DoTests([
315     ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
316     ])
317   _WaitForRapiJob(job_id)
318
319
320 def _WaitForRapiJob(job_id):
321   """Waits for a job to finish.
322
323   """
324   master = qa_config.GetMasterNode()
325
326   def _VerifyJob(data):
327     AssertEqual(data["id"], job_id)
328     for field in JOB_FIELDS:
329       AssertIn(field, data)
330
331   _DoTests([
332     ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
333     ])
334
335   return rapi.client_utils.PollJob(_rapi_client, job_id,
336                                    cli.StdioJobPollReportCb())
337
338
339 def TestRapiInstanceAdd(node, use_client):
340   """Test adding a new instance via RAPI"""
341   instance = qa_config.AcquireInstance()
342   try:
343     memory = utils.ParseUnit(qa_config.get("mem"))
344     disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
345
346     if use_client:
347       disks = [{"size": size} for size in disk_sizes]
348       nics = [{}]
349
350       beparams = {
351         constants.BE_MEMORY: memory,
352         }
353
354       job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
355                                            instance["name"],
356                                            constants.DT_PLAIN,
357                                            disks, nics,
358                                            os=qa_config.get("os"),
359                                            pnode=node["primary"],
360                                            beparams=beparams)
361     else:
362       body = {
363         "name": instance["name"],
364         "os": qa_config.get("os"),
365         "disk_template": constants.DT_PLAIN,
366         "pnode": node["primary"],
367         "memory": memory,
368         "disks": disk_sizes,
369         }
370
371       (job_id, ) = _DoTests([
372         ("/2/instances", _VerifyReturnsJob, "POST", body),
373         ])
374
375     _WaitForRapiJob(job_id)
376
377     return instance
378   except:
379     qa_config.ReleaseInstance(instance)
380     raise
381
382
383 def TestRapiInstanceRemove(instance, use_client):
384   """Test removing instance via RAPI"""
385   if use_client:
386     job_id = _rapi_client.DeleteInstance(instance["name"])
387   else:
388     (job_id, ) = _DoTests([
389       ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
390       ])
391
392   _WaitForRapiJob(job_id)
393
394   qa_config.ReleaseInstance(instance)
395
396
397 def TestRapiInstanceMigrate(instance):
398   """Test migrating instance via RAPI"""
399   # Move to secondary node
400   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
401   # And back to previous primary
402   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
403
404
405 def TestRapiInstanceRename(rename_source, rename_target):
406   """Test renaming instance via RAPI"""
407   _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
408
409
410 def TestRapiInstanceModify(instance):
411   """Test modifying instance via RAPI"""
412   def _ModifyInstance(**kwargs):
413     _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
414
415   _ModifyInstance(hvparams={
416     constants.HV_KERNEL_ARGS: "single",
417     })
418
419   _ModifyInstance(beparams={
420     constants.BE_VCPUS: 3,
421     })
422
423   _ModifyInstance(beparams={
424     constants.BE_VCPUS: constants.VALUE_DEFAULT,
425     })
426
427   _ModifyInstance(hvparams={
428     constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
429     })
430
431
432 def TestInterClusterInstanceMove(src_instance, dest_instance,
433                                  pnode, snode, tnode):
434   """Test tools/move-instance"""
435   master = qa_config.GetMasterNode()
436
437   rapi_pw_file = tempfile.NamedTemporaryFile()
438   rapi_pw_file.write(_rapi_password)
439   rapi_pw_file.flush()
440
441   # TODO: Run some instance tests before moving back
442
443   if snode is None:
444     # instance is not redundant, but we still need to pass a node
445     # (which will be ignored)
446     fsec = tnode
447   else:
448     fsec = snode
449   # note: pnode:snode are the *current* nodes, so we move it first to
450   # tnode:pnode, then back to pnode:snode
451   for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
452                           tnode["primary"], pnode["primary"]),
453                          (dest_instance["name"], src_instance["name"],
454                           pnode["primary"], fsec["primary"])]:
455     cmd = [
456       "../tools/move-instance",
457       "--verbose",
458       "--src-ca-file=%s" % _rapi_ca.name,
459       "--src-username=%s" % _rapi_username,
460       "--src-password-file=%s" % rapi_pw_file.name,
461       "--dest-instance-name=%s" % di,
462       "--dest-primary-node=%s" % pn,
463       "--dest-secondary-node=%s" % sn,
464       "--net=0:mac=%s" % constants.VALUE_GENERATE,
465       master["primary"],
466       master["primary"],
467       si,
468       ]
469
470     AssertEqual(StartLocalCommand(cmd).wait(), 0)