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