Switch job IDs to numeric
[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([("/2/query/%s" % what,
346         compat.partial(_Check, ["name", "master"]), "PUT", {
347         "fields": ["name", "master"],
348         "filter": [qlang.OP_TRUE, "master"],
349         })])
350       qresult = objects.QueryResponse.FromDict(nodes)
351       AssertEqual(qresult.data, [
352         [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
353         ])
354
355
356 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
357 def TestInstance(instance):
358   """Testing getting instance(s) info via remote API.
359
360   """
361   def _VerifyInstance(data):
362     for entry in INSTANCE_FIELDS:
363       AssertIn(entry, data)
364
365   def _VerifyInstancesList(data):
366     for instance in data:
367       for entry in LIST_FIELDS:
368         AssertIn(entry, instance)
369
370   def _VerifyInstancesBulk(data):
371     for instance_data in data:
372       _VerifyInstance(instance_data)
373
374   _DoTests([
375     ("/2/instances/%s" % instance["name"], _VerifyInstance, "GET", None),
376     ("/2/instances", _VerifyInstancesList, "GET", None),
377     ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
378     ("/2/instances/%s/activate-disks" % instance["name"],
379      _VerifyReturnsJob, "PUT", None),
380     ("/2/instances/%s/deactivate-disks" % instance["name"],
381      _VerifyReturnsJob, "PUT", None),
382     ])
383
384   # Test OpBackupPrepare
385   (job_id, ) = _DoTests([
386     ("/2/instances/%s/prepare-export?mode=%s" %
387      (instance["name"], constants.EXPORT_MODE_REMOTE),
388      _VerifyReturnsJob, "PUT", None),
389     ])
390
391   result = _WaitForRapiJob(job_id)[0]
392   AssertEqual(len(result["handshake"]), 3)
393   AssertEqual(result["handshake"][0], constants.RIE_VERSION)
394   AssertEqual(len(result["x509_key_name"]), 3)
395   AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
396
397
398 def TestNode(node):
399   """Testing getting node(s) info via remote API.
400
401   """
402   def _VerifyNode(data):
403     for entry in NODE_FIELDS:
404       AssertIn(entry, data)
405
406   def _VerifyNodesList(data):
407     for node in data:
408       for entry in LIST_FIELDS:
409         AssertIn(entry, node)
410
411   def _VerifyNodesBulk(data):
412     for node_data in data:
413       _VerifyNode(node_data)
414
415   _DoTests([
416     ("/2/nodes/%s" % node["primary"], _VerifyNode, "GET", None),
417     ("/2/nodes", _VerifyNodesList, "GET", None),
418     ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
419     ])
420
421
422 def _FilterTags(seq):
423   """Removes unwanted tags from a sequence.
424
425   """
426   ignore_re = qa_config.get("ignore-tags-re", None)
427
428   if ignore_re:
429     return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
430   else:
431     return seq
432
433
434 def TestTags(kind, name, tags):
435   """Tests .../tags resources.
436
437   """
438   if kind == constants.TAG_CLUSTER:
439     uri = "/2/tags"
440   elif kind == constants.TAG_NODE:
441     uri = "/2/nodes/%s/tags" % name
442   elif kind == constants.TAG_INSTANCE:
443     uri = "/2/instances/%s/tags" % name
444   elif kind == constants.TAG_NODEGROUP:
445     uri = "/2/groups/%s/tags" % name
446   else:
447     raise errors.ProgrammerError("Unknown tag kind")
448
449   def _VerifyTags(data):
450     AssertEqual(sorted(tags), sorted(_FilterTags(data)))
451
452   queryargs = "&".join("tag=%s" % i for i in tags)
453
454   # Add tags
455   (job_id, ) = _DoTests([
456     ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
457     ])
458   _WaitForRapiJob(job_id)
459
460   # Retrieve tags
461   _DoTests([
462     (uri, _VerifyTags, "GET", None),
463     ])
464
465   # Remove tags
466   (job_id, ) = _DoTests([
467     ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
468     ])
469   _WaitForRapiJob(job_id)
470
471
472 def _WaitForRapiJob(job_id):
473   """Waits for a job to finish.
474
475   """
476   def _VerifyJob(data):
477     AssertEqual(data["id"], job_id)
478     for field in JOB_FIELDS:
479       AssertIn(field, data)
480
481   _DoTests([
482     ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
483     ])
484
485   return rapi.client_utils.PollJob(_rapi_client, job_id,
486                                    cli.StdioJobPollReportCb())
487
488
489 def TestRapiNodeGroups():
490   """Test several node group operations using RAPI.
491
492   """
493   groups = qa_config.get("groups", {})
494   group1, group2, group3 = groups.get("inexistent-groups",
495                                       ["group1", "group2", "group3"])[:3]
496
497   # Create a group with no attributes
498   body = {
499     "name": group1,
500     }
501
502   (job_id, ) = _DoTests([
503     ("/2/groups", _VerifyReturnsJob, "POST", body),
504     ])
505
506   _WaitForRapiJob(job_id)
507
508   # Create a group specifying alloc_policy
509   body = {
510     "name": group2,
511     "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
512     }
513
514   (job_id, ) = _DoTests([
515     ("/2/groups", _VerifyReturnsJob, "POST", body),
516     ])
517
518   _WaitForRapiJob(job_id)
519
520   # Modify alloc_policy
521   body = {
522     "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
523     }
524
525   (job_id, ) = _DoTests([
526     ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
527     ])
528
529   _WaitForRapiJob(job_id)
530
531   # Rename a group
532   body = {
533     "new_name": group3,
534     }
535
536   (job_id, ) = _DoTests([
537     ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
538     ])
539
540   _WaitForRapiJob(job_id)
541
542   # Delete groups
543   for group in [group1, group3]:
544     (job_id, ) = _DoTests([
545       ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
546       ])
547
548     _WaitForRapiJob(job_id)
549
550
551 def TestRapiInstanceAdd(node, use_client):
552   """Test adding a new instance via RAPI"""
553   instance = qa_config.AcquireInstance()
554   try:
555     disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
556     disks = [{"size": size} for size in disk_sizes]
557     nic0_mac = qa_config.GetInstanceNicMac(instance,
558                                            default=constants.VALUE_GENERATE)
559     nics = [{
560       constants.INIC_MAC: nic0_mac,
561       }]
562
563     beparams = {
564       constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
565       constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
566       }
567
568     if use_client:
569       job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
570                                            instance["name"],
571                                            constants.DT_PLAIN,
572                                            disks, nics,
573                                            os=qa_config.get("os"),
574                                            pnode=node["primary"],
575                                            beparams=beparams)
576     else:
577       body = {
578         "__version__": 1,
579         "mode": constants.INSTANCE_CREATE,
580         "name": instance["name"],
581         "os_type": qa_config.get("os"),
582         "disk_template": constants.DT_PLAIN,
583         "pnode": node["primary"],
584         "beparams": beparams,
585         "disks": disks,
586         "nics": nics,
587         }
588
589       (job_id, ) = _DoTests([
590         ("/2/instances", _VerifyReturnsJob, "POST", body),
591         ])
592
593     _WaitForRapiJob(job_id)
594
595     return instance
596   except:
597     qa_config.ReleaseInstance(instance)
598     raise
599
600
601 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
602 def TestRapiInstanceRemove(instance, use_client):
603   """Test removing instance via RAPI"""
604   if use_client:
605     job_id = _rapi_client.DeleteInstance(instance["name"])
606   else:
607     (job_id, ) = _DoTests([
608       ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
609       ])
610
611   _WaitForRapiJob(job_id)
612
613   qa_config.ReleaseInstance(instance)
614
615
616 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
617 def TestRapiInstanceMigrate(instance):
618   """Test migrating instance via RAPI"""
619   # Move to secondary node
620   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
621   qa_utils.RunInstanceCheck(instance, True)
622   # And back to previous primary
623   _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
624
625
626 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
627 def TestRapiInstanceFailover(instance):
628   """Test failing over instance via RAPI"""
629   # Move to secondary node
630   _WaitForRapiJob(_rapi_client.FailoverInstance(instance["name"]))
631   qa_utils.RunInstanceCheck(instance, True)
632   # And back to previous primary
633   _WaitForRapiJob(_rapi_client.FailoverInstance(instance["name"]))
634
635
636 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
637 def TestRapiInstanceShutdown(instance):
638   """Test stopping an instance via RAPI"""
639   _WaitForRapiJob(_rapi_client.ShutdownInstance(instance["name"]))
640
641
642 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
643 def TestRapiInstanceStartup(instance):
644   """Test starting an instance via RAPI"""
645   _WaitForRapiJob(_rapi_client.StartupInstance(instance["name"]))
646
647
648 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
649 def TestRapiInstanceRenameAndBack(rename_source, rename_target):
650   """Test renaming instance via RAPI
651
652   This must leave the instance with the original name (in the
653   non-failure case).
654
655   """
656   _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
657   qa_utils.RunInstanceCheck(rename_source, False)
658   qa_utils.RunInstanceCheck(rename_target, False)
659   _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
660   qa_utils.RunInstanceCheck(rename_target, False)
661
662
663 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
664 def TestRapiInstanceReinstall(instance):
665   """Test reinstalling an instance via RAPI"""
666   _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
667   # By default, the instance is started again
668   qa_utils.RunInstanceCheck(instance, True)
669
670   # Reinstall again without starting
671   _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"],
672                                                  no_startup=True))
673
674
675 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
676 def TestRapiInstanceReplaceDisks(instance):
677   """Test replacing instance disks via RAPI"""
678   _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
679     mode=constants.REPLACE_DISK_AUTO, disks=[]))
680   _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
681     mode=constants.REPLACE_DISK_SEC, disks="0"))
682
683
684 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
685 def TestRapiInstanceModify(instance):
686   """Test modifying instance via RAPI"""
687   default_hv = qa_config.GetDefaultHypervisor()
688
689   def _ModifyInstance(**kwargs):
690     _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
691
692   _ModifyInstance(beparams={
693     constants.BE_VCPUS: 3,
694     })
695
696   _ModifyInstance(beparams={
697     constants.BE_VCPUS: constants.VALUE_DEFAULT,
698     })
699
700   if default_hv == constants.HT_XEN_PVM:
701     _ModifyInstance(hvparams={
702       constants.HV_KERNEL_ARGS: "single",
703       })
704     _ModifyInstance(hvparams={
705       constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
706       })
707   elif default_hv == constants.HT_XEN_HVM:
708     _ModifyInstance(hvparams={
709       constants.HV_BOOT_ORDER: "acn",
710       })
711     _ModifyInstance(hvparams={
712       constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
713       })
714
715
716 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
717 def TestRapiInstanceConsole(instance):
718   """Test getting instance console information via RAPI"""
719   result = _rapi_client.GetInstanceConsole(instance["name"])
720   console = objects.InstanceConsole.FromDict(result)
721   AssertEqual(console.Validate(), True)
722   AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance["name"]))
723
724
725 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
726 def TestRapiStoppedInstanceConsole(instance):
727   """Test getting stopped instance's console information via RAPI"""
728   try:
729     _rapi_client.GetInstanceConsole(instance["name"])
730   except rapi.client.GanetiApiError, err:
731     AssertEqual(err.code, 503)
732   else:
733     raise qa_error.Error("Getting console for stopped instance didn't"
734                          " return HTTP 503")
735
736
737 def GetOperatingSystems():
738   """Retrieves a list of all available operating systems.
739
740   """
741   return _rapi_client.GetOperatingSystems()
742
743
744 def TestInterClusterInstanceMove(src_instance, dest_instance,
745                                  pnode, snode, tnode):
746   """Test tools/move-instance"""
747   master = qa_config.GetMasterNode()
748
749   rapi_pw_file = tempfile.NamedTemporaryFile()
750   rapi_pw_file.write(_rapi_password)
751   rapi_pw_file.flush()
752
753   # TODO: Run some instance tests before moving back
754
755   if snode is None:
756     # instance is not redundant, but we still need to pass a node
757     # (which will be ignored)
758     fsec = tnode
759   else:
760     fsec = snode
761   # note: pnode:snode are the *current* nodes, so we move it first to
762   # tnode:pnode, then back to pnode:snode
763   for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
764                           tnode["primary"], pnode["primary"]),
765                          (dest_instance["name"], src_instance["name"],
766                           pnode["primary"], fsec["primary"])]:
767     cmd = [
768       "../tools/move-instance",
769       "--verbose",
770       "--src-ca-file=%s" % _rapi_ca.name,
771       "--src-username=%s" % _rapi_username,
772       "--src-password-file=%s" % rapi_pw_file.name,
773       "--dest-instance-name=%s" % di,
774       "--dest-primary-node=%s" % pn,
775       "--dest-secondary-node=%s" % sn,
776       "--net=0:mac=%s" % constants.VALUE_GENERATE,
777       master["primary"],
778       master["primary"],
779       si,
780       ]
781
782     qa_utils.RunInstanceCheck(di, False)
783     AssertEqual(StartLocalCommand(cmd).wait(), 0)
784     qa_utils.RunInstanceCheck(si, False)
785     qa_utils.RunInstanceCheck(di, True)