Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / qa / qa_rapi.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 """Remote API QA tests.
23
24 """
25
26 import tempfile
27 import random
28 import re
29 import itertools
30
31 from ganeti import utils
32 from ganeti import constants
33 from ganeti import errors
34 from ganeti import cli
35 from ganeti import rapi
36 from ganeti import objects
37 from ganeti import query
38 from ganeti import compat
39 from ganeti import qlang
40 from ganeti import pathutils
41
42 import ganeti.rapi.client        # pylint: disable=W0611
43 import ganeti.rapi.client_utils
44
45 import qa_config
46 import qa_utils
47 import qa_error
48
49 from qa_instance import IsFailoverSupported
50 from qa_instance import IsMigrationSupported
51 from qa_instance import IsDiskReplacingSupported
52 from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
53 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG
54
55
56 _rapi_ca = None
57 _rapi_client = None
58 _rapi_username = None
59 _rapi_password = None
60
61
62 def Setup(username, password):
63   """Configures the RAPI client.
64
65   """
66   # pylint: disable=W0603
67   # due to global usage
68   global _rapi_ca
69   global _rapi_client
70   global _rapi_username
71   global _rapi_password
72
73   _rapi_username = username
74   _rapi_password = password
75
76   master = qa_config.GetMasterNode()
77
78   # Load RAPI certificate from master node
79   cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_CERT_FILE)]
80
81   # Write to temporary file
82   _rapi_ca = tempfile.NamedTemporaryFile()
83   _rapi_ca.write(qa_utils.GetCommandOutput(master.primary,
84                                            utils.ShellQuoteArgs(cmd)))
85   _rapi_ca.flush()
86
87   port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
88   cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
89                                            proxy="")
90
91   if qa_config.UseVirtualCluster():
92     # TODO: Implement full support for RAPI on virtual clusters
93     print qa_utils.FormatWarning("RAPI tests are not yet supported on"
94                                  " virtual clusters and will be disabled")
95
96     assert _rapi_client is None
97   else:
98     _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port,
99                                                 username=username,
100                                                 password=password,
101                                                 curl_config_fn=cfg_curl)
102
103     print "RAPI protocol version: %s" % _rapi_client.GetVersion()
104
105
106 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
107                    "admin_state",
108                    "disk_template", "disk.sizes", "disk.spindles",
109                    "nic.ips", "nic.macs", "nic.modes", "nic.links",
110                    "beparams", "hvparams",
111                    "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
112
113 NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
114                "mtotal", "mnode", "mfree",
115                "pinst_cnt", "sinst_cnt", "tags")
116
117 GROUP_FIELDS = compat.UniqueFrozenset([
118   "name", "uuid",
119   "alloc_policy",
120   "node_cnt", "node_list",
121   ])
122
123 JOB_FIELDS = compat.UniqueFrozenset([
124   "id", "ops", "status", "summary",
125   "opstatus", "opresult", "oplog",
126   "received_ts", "start_ts", "end_ts",
127   ])
128
129 LIST_FIELDS = ("id", "uri")
130
131
132 def Enabled():
133   """Return whether remote API tests should be run.
134
135   """
136   # TODO: Implement RAPI tests for virtual clusters
137   return (qa_config.TestEnabled("rapi") and
138           not qa_config.UseVirtualCluster())
139
140
141 def _DoTests(uris):
142   # pylint: disable=W0212
143   # due to _SendRequest usage
144   results = []
145
146   for uri, verify, method, body in uris:
147     assert uri.startswith("/")
148
149     print "%s %s" % (method, uri)
150     data = _rapi_client._SendRequest(method, uri, None, body)
151
152     if verify is not None:
153       if callable(verify):
154         verify(data)
155       else:
156         AssertEqual(data, verify)
157
158     results.append(data)
159
160   return results
161
162
163 def _VerifyReturnsJob(data):
164   if not isinstance(data, int):
165     AssertMatch(data, r"^\d+$")
166
167
168 def TestVersion():
169   """Testing remote API version.
170
171   """
172   _DoTests([
173     ("/version", constants.RAPI_VERSION, "GET", None),
174     ])
175
176
177 def TestEmptyCluster():
178   """Testing remote API on an empty cluster.
179
180   """
181   master = qa_config.GetMasterNode()
182   master_full = qa_utils.ResolveNodeName(master)
183
184   def _VerifyInfo(data):
185     AssertIn("name", data)
186     AssertIn("master", data)
187     AssertEqual(data["master"], master_full)
188
189   def _VerifyNodes(data):
190     master_entry = {
191       "id": master_full,
192       "uri": "/2/nodes/%s" % master_full,
193       }
194     AssertIn(master_entry, data)
195
196   def _VerifyNodesBulk(data):
197     for node in data:
198       for entry in NODE_FIELDS:
199         AssertIn(entry, node)
200
201   def _VerifyGroups(data):
202     default_group = {
203       "name": constants.INITIAL_NODE_GROUP_NAME,
204       "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
205       }
206     AssertIn(default_group, data)
207
208   def _VerifyGroupsBulk(data):
209     for group in data:
210       for field in GROUP_FIELDS:
211         AssertIn(field, group)
212
213   _DoTests([
214     ("/", None, "GET", None),
215     ("/2/info", _VerifyInfo, "GET", None),
216     ("/2/tags", None, "GET", None),
217     ("/2/nodes", _VerifyNodes, "GET", None),
218     ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
219     ("/2/groups", _VerifyGroups, "GET", None),
220     ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
221     ("/2/instances", [], "GET", None),
222     ("/2/instances?bulk=1", [], "GET", None),
223     ("/2/os", None, "GET", None),
224     ])
225
226   # Test HTTP Not Found
227   for method in ["GET", "PUT", "POST", "DELETE"]:
228     try:
229       _DoTests([("/99/resource/not/here/99", None, method, None)])
230     except rapi.client.GanetiApiError, err:
231       AssertEqual(err.code, 404)
232     else:
233       raise qa_error.Error("Non-existent resource didn't return HTTP 404")
234
235   # Test HTTP Not Implemented
236   for method in ["PUT", "POST", "DELETE"]:
237     try:
238       _DoTests([("/version", None, method, None)])
239     except rapi.client.GanetiApiError, err:
240       AssertEqual(err.code, 501)
241     else:
242       raise qa_error.Error("Non-implemented method didn't fail")
243
244
245 def TestRapiQuery():
246   """Testing resource queries via remote API.
247
248   """
249   # FIXME: the tests are failing if no LVM is enabled, investigate
250   # if it is a bug in the QA or in the code
251   if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
252     return
253
254   master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
255   rnd = random.Random(7818)
256
257   for what in constants.QR_VIA_RAPI:
258     if what == constants.QR_JOB:
259       namefield = "id"
260     elif what == constants.QR_EXPORT:
261       namefield = "export"
262     else:
263       namefield = "name"
264
265     all_fields = query.ALL_FIELDS[what].keys()
266     rnd.shuffle(all_fields)
267
268     # No fields, should return everything
269     result = _rapi_client.QueryFields(what)
270     qresult = objects.QueryFieldsResponse.FromDict(result)
271     AssertEqual(len(qresult.fields), len(all_fields))
272
273     # One field
274     result = _rapi_client.QueryFields(what, fields=[namefield])
275     qresult = objects.QueryFieldsResponse.FromDict(result)
276     AssertEqual(len(qresult.fields), 1)
277
278     # Specify all fields, order must be correct
279     result = _rapi_client.QueryFields(what, fields=all_fields)
280     qresult = objects.QueryFieldsResponse.FromDict(result)
281     AssertEqual(len(qresult.fields), len(all_fields))
282     AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
283
284     # Unknown field
285     result = _rapi_client.QueryFields(what, fields=["_unknown!"])
286     qresult = objects.QueryFieldsResponse.FromDict(result)
287     AssertEqual(len(qresult.fields), 1)
288     AssertEqual(qresult.fields[0].name, "_unknown!")
289     AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
290
291     # Try once more, this time without the client
292     _DoTests([
293       ("/2/query/%s/fields" % what, None, "GET", None),
294       ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
295        None, "GET", None),
296       ])
297
298     # Try missing query argument
299     try:
300       _DoTests([
301         ("/2/query/%s" % what, None, "GET", None),
302         ])
303     except rapi.client.GanetiApiError, err:
304       AssertEqual(err.code, 400)
305     else:
306       raise qa_error.Error("Request missing 'fields' parameter didn't fail")
307
308     def _Check(exp_fields, data):
309       qresult = objects.QueryResponse.FromDict(data)
310       AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
311       if not isinstance(qresult.data, list):
312         raise qa_error.Error("Query did not return a list")
313
314     _DoTests([
315       # Specify fields in query
316       ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
317        compat.partial(_Check, all_fields), "GET", None),
318
319       ("/2/query/%s?fields=%s" % (what, namefield),
320        compat.partial(_Check, [namefield]), "GET", None),
321
322       # Note the spaces
323       ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
324        (what, namefield, namefield, namefield),
325        compat.partial(_Check, [namefield] * 3), "GET", None),
326
327       # PUT with fields in query
328       ("/2/query/%s?fields=%s" % (what, namefield),
329        compat.partial(_Check, [namefield]), "PUT", {}),
330
331       ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
332          "fields": [namefield] * 4,
333          }),
334
335       ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
336          "fields": all_fields,
337          }),
338
339       ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
340          "fields": [namefield] * 4
341          })])
342
343     def _CheckFilter():
344       _DoTests([
345         # With filter
346         ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
347            "fields": all_fields,
348            "filter": [qlang.OP_TRUE, namefield],
349            }),
350         ])
351
352     if what == constants.QR_LOCK:
353       # Locks can't be filtered
354       try:
355         _CheckFilter()
356       except rapi.client.GanetiApiError, err:
357         AssertEqual(err.code, 500)
358       else:
359         raise qa_error.Error("Filtering locks didn't fail")
360     else:
361       _CheckFilter()
362
363     if what == constants.QR_NODE:
364       # Test with filter
365       (nodes, ) = _DoTests(
366         [("/2/query/%s" % what,
367           compat.partial(_Check, ["name", "master"]), "PUT",
368           {"fields": ["name", "master"],
369            "filter": [qlang.OP_TRUE, "master"],
370            })])
371       qresult = objects.QueryResponse.FromDict(nodes)
372       AssertEqual(qresult.data, [
373         [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
374         ])
375
376
377 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
378 def TestInstance(instance):
379   """Testing getting instance(s) info via remote API.
380
381   """
382   def _VerifyInstance(data):
383     for entry in INSTANCE_FIELDS:
384       AssertIn(entry, data)
385
386   def _VerifyInstancesList(data):
387     for instance in data:
388       for entry in LIST_FIELDS:
389         AssertIn(entry, instance)
390
391   def _VerifyInstancesBulk(data):
392     for instance_data in data:
393       _VerifyInstance(instance_data)
394
395   _DoTests([
396     ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
397     ("/2/instances", _VerifyInstancesList, "GET", None),
398     ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
399     ("/2/instances/%s/activate-disks" % instance.name,
400      _VerifyReturnsJob, "PUT", None),
401     ("/2/instances/%s/deactivate-disks" % instance.name,
402      _VerifyReturnsJob, "PUT", None),
403     ])
404
405   # Test OpBackupPrepare
406   (job_id, ) = _DoTests([
407     ("/2/instances/%s/prepare-export?mode=%s" %
408      (instance.name, constants.EXPORT_MODE_REMOTE),
409      _VerifyReturnsJob, "PUT", None),
410     ])
411
412   result = _WaitForRapiJob(job_id)[0]
413   AssertEqual(len(result["handshake"]), 3)
414   AssertEqual(result["handshake"][0], constants.RIE_VERSION)
415   AssertEqual(len(result["x509_key_name"]), 3)
416   AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
417
418
419 def TestNode(node):
420   """Testing getting node(s) info via remote API.
421
422   """
423   def _VerifyNode(data):
424     for entry in NODE_FIELDS:
425       AssertIn(entry, data)
426
427   def _VerifyNodesList(data):
428     for node in data:
429       for entry in LIST_FIELDS:
430         AssertIn(entry, node)
431
432   def _VerifyNodesBulk(data):
433     for node_data in data:
434       _VerifyNode(node_data)
435
436   _DoTests([
437     ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
438     ("/2/nodes", _VerifyNodesList, "GET", None),
439     ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
440     ])
441
442
443 def _FilterTags(seq):
444   """Removes unwanted tags from a sequence.
445
446   """
447   ignore_re = qa_config.get("ignore-tags-re", None)
448
449   if ignore_re:
450     return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
451   else:
452     return seq
453
454
455 def TestTags(kind, name, tags):
456   """Tests .../tags resources.
457
458   """
459   if kind == constants.TAG_CLUSTER:
460     uri = "/2/tags"
461   elif kind == constants.TAG_NODE:
462     uri = "/2/nodes/%s/tags" % name
463   elif kind == constants.TAG_INSTANCE:
464     uri = "/2/instances/%s/tags" % name
465   elif kind == constants.TAG_NODEGROUP:
466     uri = "/2/groups/%s/tags" % name
467   elif kind == constants.TAG_NETWORK:
468     uri = "/2/networks/%s/tags" % name
469   else:
470     raise errors.ProgrammerError("Unknown tag kind")
471
472   def _VerifyTags(data):
473     AssertEqual(sorted(tags), sorted(_FilterTags(data)))
474
475   queryargs = "&".join("tag=%s" % i for i in tags)
476
477   # Add tags
478   (job_id, ) = _DoTests([
479     ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
480     ])
481   _WaitForRapiJob(job_id)
482
483   # Retrieve tags
484   _DoTests([
485     (uri, _VerifyTags, "GET", None),
486     ])
487
488   # Remove tags
489   (job_id, ) = _DoTests([
490     ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
491     ])
492   _WaitForRapiJob(job_id)
493
494
495 def _WaitForRapiJob(job_id):
496   """Waits for a job to finish.
497
498   """
499   def _VerifyJob(data):
500     AssertEqual(data["id"], job_id)
501     for field in JOB_FIELDS:
502       AssertIn(field, data)
503
504   _DoTests([
505     ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
506     ])
507
508   return rapi.client_utils.PollJob(_rapi_client, job_id,
509                                    cli.StdioJobPollReportCb())
510
511
512 def TestRapiNodeGroups():
513   """Test several node group operations using RAPI.
514
515   """
516   (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
517
518   # Create a group with no attributes
519   body = {
520     "name": group1,
521     }
522
523   (job_id, ) = _DoTests([
524     ("/2/groups", _VerifyReturnsJob, "POST", body),
525     ])
526
527   _WaitForRapiJob(job_id)
528
529   # Create a group specifying alloc_policy
530   body = {
531     "name": group2,
532     "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
533     }
534
535   (job_id, ) = _DoTests([
536     ("/2/groups", _VerifyReturnsJob, "POST", body),
537     ])
538
539   _WaitForRapiJob(job_id)
540
541   # Modify alloc_policy
542   body = {
543     "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
544     }
545
546   (job_id, ) = _DoTests([
547     ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
548     ])
549
550   _WaitForRapiJob(job_id)
551
552   # Rename a group
553   body = {
554     "new_name": group3,
555     }
556
557   (job_id, ) = _DoTests([
558     ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
559     ])
560
561   _WaitForRapiJob(job_id)
562
563   # Delete groups
564   for group in [group1, group3]:
565     (job_id, ) = _DoTests([
566       ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
567       ])
568
569     _WaitForRapiJob(job_id)
570
571
572 def TestRapiInstanceAdd(node, use_client):
573   """Test adding a new instance via RAPI"""
574   if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
575     return
576   instance = qa_config.AcquireInstance()
577   instance.SetDiskTemplate(constants.DT_PLAIN)
578   try:
579     disks = [{"size": utils.ParseUnit(d.get("size")),
580               "name": str(d.get("name"))}
581              for d in qa_config.GetDiskOptions()]
582     nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
583     nics = [{
584       constants.INIC_MAC: nic0_mac,
585       }]
586
587     beparams = {
588       constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
589       constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
590       }
591
592     if use_client:
593       job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
594                                            instance.name,
595                                            constants.DT_PLAIN,
596                                            disks, nics,
597                                            os=qa_config.get("os"),
598                                            pnode=node.primary,
599                                            beparams=beparams)
600     else:
601       body = {
602         "__version__": 1,
603         "mode": constants.INSTANCE_CREATE,
604         "name": instance.name,
605         "os_type": qa_config.get("os"),
606         "disk_template": constants.DT_PLAIN,
607         "pnode": node.primary,
608         "beparams": beparams,
609         "disks": disks,
610         "nics": nics,
611         }
612
613       (job_id, ) = _DoTests([
614         ("/2/instances", _VerifyReturnsJob, "POST", body),
615         ])
616
617     _WaitForRapiJob(job_id)
618
619     return instance
620   except:
621     instance.Release()
622     raise
623
624
625 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
626 def TestRapiInstanceRemove(instance, use_client):
627   """Test removing instance via RAPI"""
628   # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
629   # in RAPI or in the test
630   if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
631     return
632
633   if use_client:
634     job_id = _rapi_client.DeleteInstance(instance.name)
635   else:
636     (job_id, ) = _DoTests([
637       ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
638       ])
639
640   _WaitForRapiJob(job_id)
641
642
643 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
644 def TestRapiInstanceMigrate(instance):
645   """Test migrating instance via RAPI"""
646   if not IsMigrationSupported(instance):
647     print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
648                               " test")
649     return
650   # Move to secondary node
651   _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
652   qa_utils.RunInstanceCheck(instance, True)
653   # And back to previous primary
654   _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
655
656
657 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
658 def TestRapiInstanceFailover(instance):
659   """Test failing over instance via RAPI"""
660   if not IsFailoverSupported(instance):
661     print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
662                               " test")
663     return
664   # Move to secondary node
665   _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
666   qa_utils.RunInstanceCheck(instance, True)
667   # And back to previous primary
668   _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
669
670
671 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
672 def TestRapiInstanceShutdown(instance):
673   """Test stopping an instance via RAPI"""
674   _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
675
676
677 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
678 def TestRapiInstanceStartup(instance):
679   """Test starting an instance via RAPI"""
680   _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
681
682
683 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
684 def TestRapiInstanceRenameAndBack(rename_source, rename_target):
685   """Test renaming instance via RAPI
686
687   This must leave the instance with the original name (in the
688   non-failure case).
689
690   """
691   _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
692   qa_utils.RunInstanceCheck(rename_source, False)
693   qa_utils.RunInstanceCheck(rename_target, False)
694   _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
695   qa_utils.RunInstanceCheck(rename_target, False)
696
697
698 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
699 def TestRapiInstanceReinstall(instance):
700   """Test reinstalling an instance via RAPI"""
701   if instance.disk_template == constants.DT_DISKLESS:
702     print qa_utils.FormatInfo("Test not supported for diskless instances")
703     return
704
705   _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
706   # By default, the instance is started again
707   qa_utils.RunInstanceCheck(instance, True)
708
709   # Reinstall again without starting
710   _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
711                                                  no_startup=True))
712
713
714 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
715 def TestRapiInstanceReplaceDisks(instance):
716   """Test replacing instance disks via RAPI"""
717   if not IsDiskReplacingSupported(instance):
718     print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
719                               " skipping test")
720     return
721   fn = _rapi_client.ReplaceInstanceDisks
722   _WaitForRapiJob(fn(instance.name,
723                      mode=constants.REPLACE_DISK_AUTO, disks=[]))
724   _WaitForRapiJob(fn(instance.name,
725                      mode=constants.REPLACE_DISK_SEC, disks="0"))
726
727
728 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
729 def TestRapiInstanceModify(instance):
730   """Test modifying instance via RAPI"""
731   default_hv = qa_config.GetDefaultHypervisor()
732
733   def _ModifyInstance(**kwargs):
734     _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
735
736   _ModifyInstance(beparams={
737     constants.BE_VCPUS: 3,
738     })
739
740   _ModifyInstance(beparams={
741     constants.BE_VCPUS: constants.VALUE_DEFAULT,
742     })
743
744   if default_hv == constants.HT_XEN_PVM:
745     _ModifyInstance(hvparams={
746       constants.HV_KERNEL_ARGS: "single",
747       })
748     _ModifyInstance(hvparams={
749       constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
750       })
751   elif default_hv == constants.HT_XEN_HVM:
752     _ModifyInstance(hvparams={
753       constants.HV_BOOT_ORDER: "acn",
754       })
755     _ModifyInstance(hvparams={
756       constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
757       })
758
759
760 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
761 def TestRapiInstanceConsole(instance):
762   """Test getting instance console information via RAPI"""
763   result = _rapi_client.GetInstanceConsole(instance.name)
764   console = objects.InstanceConsole.FromDict(result)
765   AssertEqual(console.Validate(), True)
766   AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
767
768
769 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
770 def TestRapiStoppedInstanceConsole(instance):
771   """Test getting stopped instance's console information via RAPI"""
772   try:
773     _rapi_client.GetInstanceConsole(instance.name)
774   except rapi.client.GanetiApiError, err:
775     AssertEqual(err.code, 503)
776   else:
777     raise qa_error.Error("Getting console for stopped instance didn't"
778                          " return HTTP 503")
779
780
781 def GetOperatingSystems():
782   """Retrieves a list of all available operating systems.
783
784   """
785   return _rapi_client.GetOperatingSystems()
786
787
788 def TestInterClusterInstanceMove(src_instance, dest_instance,
789                                  inodes, tnode):
790   """Test tools/move-instance"""
791   master = qa_config.GetMasterNode()
792
793   rapi_pw_file = tempfile.NamedTemporaryFile()
794   rapi_pw_file.write(_rapi_password)
795   rapi_pw_file.flush()
796
797   dest_instance.SetDiskTemplate(src_instance.disk_template)
798
799   # TODO: Run some instance tests before moving back
800
801   if len(inodes) > 1:
802     # No disk template currently requires more than 1 secondary node. If this
803     # changes, either this test must be skipped or the script must be updated.
804     assert len(inodes) == 2
805     snode = inodes[1]
806   else:
807     # instance is not redundant, but we still need to pass a node
808     # (which will be ignored)
809     snode = tnode
810   pnode = inodes[0]
811   # note: pnode:snode are the *current* nodes, so we move it first to
812   # tnode:pnode, then back to pnode:snode
813   for si, di, pn, sn in [(src_instance.name, dest_instance.name,
814                           tnode.primary, pnode.primary),
815                          (dest_instance.name, src_instance.name,
816                           pnode.primary, snode.primary)]:
817     cmd = [
818       "../tools/move-instance",
819       "--verbose",
820       "--src-ca-file=%s" % _rapi_ca.name,
821       "--src-username=%s" % _rapi_username,
822       "--src-password-file=%s" % rapi_pw_file.name,
823       "--dest-instance-name=%s" % di,
824       "--dest-primary-node=%s" % pn,
825       "--dest-secondary-node=%s" % sn,
826       "--net=0:mac=%s" % constants.VALUE_GENERATE,
827       master.primary,
828       master.primary,
829       si,
830       ]
831
832     qa_utils.RunInstanceCheck(di, False)
833     AssertEqual(StartLocalCommand(cmd).wait(), 0)
834     qa_utils.RunInstanceCheck(si, False)
835     qa_utils.RunInstanceCheck(di, True)