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