Try again to fix the inter-cluster move 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 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
182 def TestInstance(instance):
183   """Testing getting instance(s) info via remote API.
184
185   """
186   def _VerifyInstance(data):
187     for entry in INSTANCE_FIELDS:
188       AssertIn(entry, data)
189
190   def _VerifyInstancesList(data):
191     for instance in data:
192       for entry in LIST_FIELDS:
193         AssertIn(entry, instance)
194
195   def _VerifyInstancesBulk(data):
196     for instance_data in data:
197       _VerifyInstance(instance_data)
198
199   _DoTests([
200     ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
201     ("/2/instances", _VerifyInstancesList, 'GET', None),
202     ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
203     ("/2/instances/%s/activate-disks" % instance["name"],
204      _VerifyReturnsJob, 'PUT', None),
205     ("/2/instances/%s/deactivate-disks" % instance["name"],
206      _VerifyReturnsJob, 'PUT', None),
207     ])
208
209   # Test OpPrepareExport
210   (job_id, ) = _DoTests([
211     ("/2/instances/%s/prepare-export?mode=%s" %
212      (instance["name"], constants.EXPORT_MODE_REMOTE),
213      _VerifyReturnsJob, "PUT", None),
214     ])
215
216   result = _WaitForRapiJob(job_id)[0]
217   AssertEqual(len(result["handshake"]), 3)
218   AssertEqual(result["handshake"][0], constants.RIE_VERSION)
219   AssertEqual(len(result["x509_key_name"]), 3)
220   AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
221
222
223 def TestNode(node):
224   """Testing getting node(s) info via remote API.
225
226   """
227   def _VerifyNode(data):
228     for entry in NODE_FIELDS:
229       AssertIn(entry, data)
230
231   def _VerifyNodesList(data):
232     for node in data:
233       for entry in LIST_FIELDS:
234         AssertIn(entry, node)
235
236   def _VerifyNodesBulk(data):
237     for node_data in data:
238       _VerifyNode(node_data)
239
240   _DoTests([
241     ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
242     ("/2/nodes", _VerifyNodesList, 'GET', None),
243     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
244     ])
245
246
247 def TestTags(kind, name, tags):
248   """Tests .../tags resources.
249
250   """
251   if kind == constants.TAG_CLUSTER:
252     uri = "/2/tags"
253   elif kind == constants.TAG_NODE:
254     uri = "/2/nodes/%s/tags" % name
255   elif kind == constants.TAG_INSTANCE:
256     uri = "/2/instances/%s/tags" % name
257   else:
258     raise errors.ProgrammerError("Unknown tag kind")
259
260   def _VerifyTags(data):
261     AssertEqual(sorted(tags), sorted(data))
262
263   query = "&".join("tag=%s" % i for i in tags)
264
265   # Add tags
266   (job_id, ) = _DoTests([
267     ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
268     ])
269   _WaitForRapiJob(job_id)
270
271   # Retrieve tags
272   _DoTests([
273     (uri, _VerifyTags, 'GET', None),
274     ])
275
276   # Remove tags
277   (job_id, ) = _DoTests([
278     ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
279     ])
280   _WaitForRapiJob(job_id)
281
282
283 def _WaitForRapiJob(job_id):
284   """Waits for a job to finish.
285
286   """
287   master = qa_config.GetMasterNode()
288
289   def _VerifyJob(data):
290     AssertEqual(data["id"], job_id)
291     for field in JOB_FIELDS:
292       AssertIn(field, data)
293
294   _DoTests([
295     ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
296     ])
297
298   return rapi.client_utils.PollJob(_rapi_client, job_id,
299                                    cli.StdioJobPollReportCb())
300
301
302 def TestRapiInstanceAdd(node, use_client):
303   """Test adding a new instance via RAPI"""
304   instance = qa_config.AcquireInstance()
305   try:
306     memory = utils.ParseUnit(qa_config.get("mem"))
307     disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
308
309     if use_client:
310       disks = [{"size": size} for size in disk_sizes]
311       nics = [{}]
312
313       beparams = {
314         constants.BE_MEMORY: memory,
315         }
316
317       job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
318                                            instance["name"],
319                                            constants.DT_PLAIN,
320                                            disks, nics,
321                                            os=qa_config.get("os"),
322                                            pnode=node["primary"],
323                                            beparams=beparams)
324     else:
325       body = {
326         "name": instance["name"],
327         "os": qa_config.get("os"),
328         "disk_template": constants.DT_PLAIN,
329         "pnode": node["primary"],
330         "memory": memory,
331         "disks": disk_sizes,
332         }
333
334       (job_id, ) = _DoTests([
335         ("/2/instances", _VerifyReturnsJob, "POST", body),
336         ])
337
338     _WaitForRapiJob(job_id)
339
340     return instance
341   except:
342     qa_config.ReleaseInstance(instance)
343     raise
344
345
346 def TestRapiInstanceRemove(instance, use_client):
347   """Test removing instance via RAPI"""
348   if use_client:
349     job_id = _rapi_client.DeleteInstance(instance["name"])
350   else:
351     (job_id, ) = _DoTests([
352       ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
353       ])
354
355   _WaitForRapiJob(job_id)
356
357   qa_config.ReleaseInstance(instance)
358
359
360 def TestRapiInstanceMigrate(instance):
361   """Test migrating instance via RAPI"""
362   # Move to secondary node
363   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
364   # And back to previous primary
365   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
366
367
368 def TestRapiInstanceRename(instance, rename_target):
369   """Test renaming instance via RAPI"""
370   rename_source = instance["name"]
371
372   for name1, name2 in [(rename_source, rename_target),
373                        (rename_target, rename_source)]:
374     _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
375
376
377 def TestRapiInstanceModify(instance):
378   """Test modifying instance via RAPI"""
379   def _ModifyInstance(**kwargs):
380     _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
381
382   _ModifyInstance(hvparams={
383     constants.HV_KERNEL_ARGS: "single",
384     })
385
386   _ModifyInstance(beparams={
387     constants.BE_VCPUS: 3,
388     })
389
390   _ModifyInstance(beparams={
391     constants.BE_VCPUS: constants.VALUE_DEFAULT,
392     })
393
394   _ModifyInstance(hvparams={
395     constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
396     })
397
398
399 def TestInterClusterInstanceMove(src_instance, dest_instance,
400                                  pnode, snode, tnode):
401   """Test tools/move-instance"""
402   master = qa_config.GetMasterNode()
403
404   rapi_pw_file = tempfile.NamedTemporaryFile()
405   rapi_pw_file.write(_rapi_password)
406   rapi_pw_file.flush()
407
408   # TODO: Run some instance tests before moving back
409
410   if snode is None:
411     # instance is not redundant, but we still need to pass a node
412     # (which will be ignored)
413     fsec = tnode
414   else:
415     fsec = snode
416   # note: pnode:snode are the *current* nodes, so we move it first to
417   # tnode:pnode, then back to pnode:snode
418   for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
419                           tnode["primary"], pnode["primary"]),
420                          (dest_instance["name"], src_instance["name"],
421                           pnode["primary"], fsec["primary"])]:
422     cmd = [
423       "../tools/move-instance",
424       "--verbose",
425       "--src-ca-file=%s" % _rapi_ca.name,
426       "--src-username=%s" % _rapi_username,
427       "--src-password-file=%s" % rapi_pw_file.name,
428       "--dest-instance-name=%s" % di,
429       "--dest-primary-node=%s" % pn,
430       "--dest-secondary-node=%s" % sn,
431       "--net=0:mac=%s" % constants.VALUE_GENERATE,
432       master["primary"],
433       master["primary"],
434       si,
435       ]
436
437     AssertEqual(StartLocalCommand(cmd).wait(), 0)