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