Merge branch 'stable-2.12' into stable-2.13
[ganeti-github.git] / qa / qa_rapi.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Remote API QA tests.
32
33 """
34
35 import copy
36 import functools
37 import itertools
38 import os.path
39 import random
40 import re
41 import tempfile
42 import uuid as uuid_module
43
44 from ganeti import cli
45 from ganeti import compat
46 from ganeti import constants
47 from ganeti import errors
48 from ganeti import locking
49 from ganeti import objects
50 from ganeti import opcodes
51 from ganeti import pathutils
52 from ganeti import qlang
53 from ganeti import query
54 from ganeti import rapi
55 from ganeti import utils
56
57 from ganeti.http.auth import ParsePasswordFile
58 import ganeti.rapi.client # pylint: disable=W0611
59 import ganeti.rapi.client_utils
60
61 import qa_config
62 import qa_error
63 import qa_logging
64 import qa_utils
65
66 from qa_instance import GetInstanceInfo
67 from qa_instance import IsDiskReplacingSupported
68 from qa_instance import IsFailoverSupported
69 from qa_instance import IsMigrationSupported
70 from qa_job_utils import RunWithLocks
71 from qa_utils import (AssertEqual, AssertIn, AssertMatch, AssertCommand,
72 StartLocalCommand)
73 from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG
74
75
76 _rapi_ca = None
77 _rapi_client = None
78 _rapi_username = None
79 _rapi_password = None
80
81 # The files to copy if the RAPI files QA config value is set
82 _FILES_TO_COPY = [
83 pathutils.CLUSTER_DOMAIN_SECRET_FILE,
84 pathutils.RAPI_CERT_FILE,
85 pathutils.RAPI_USERS_FILE,
86 ]
87
88
89 def _EnsureRapiFilesPresence():
90 """Ensures that the specified RAPI files are present on the cluster, if any.
91
92 """
93 rapi_files_location = qa_config.get("rapi-files-location", None)
94 if rapi_files_location is None:
95 # No files to be had
96 return
97
98 print qa_logging.FormatWarning("Replacing the certificate and users file on"
99 " the node with the ones provided in %s"
100 % rapi_files_location)
101
102 # The RAPI files
103 AssertCommand(["mkdir", "-p", pathutils.RAPI_DATA_DIR])
104
105 for filename in _FILES_TO_COPY:
106 basename = os.path.split(filename)[-1]
107 AssertCommand(["cp", os.path.join(rapi_files_location, basename),
108 filename])
109 AssertCommand(["gnt-cluster", "copyfile", filename])
110
111 # The certificates have to be reloaded now
112 AssertCommand(["service", "ganeti", "restart"])
113
114
115 def ReloadCertificates(ensure_presence=True):
116 """Reloads the client RAPI certificate with the one present on the node.
117
118 If the QA is set up to use a specific certificate using the
119 "rapi-files-location" parameter, it will be put in place prior to retrieving
120 it.
121
122 """
123 if ensure_presence:
124 _EnsureRapiFilesPresence()
125
126 if _rapi_username is None or _rapi_password is None:
127 raise qa_error.Error("RAPI username and password have to be set before"
128 " attempting to reload a certificate.")
129
130 # pylint: disable=W0603
131 # due to global usage
132 global _rapi_ca
133 global _rapi_client
134
135 master = qa_config.GetMasterNode()
136
137 # Load RAPI certificate from master node
138 cmd = ["openssl", "x509", "-in",
139 qa_utils.MakeNodePath(master, pathutils.RAPI_CERT_FILE)]
140
141 # Write to temporary file
142 _rapi_ca = tempfile.NamedTemporaryFile()
143 _rapi_ca.write(qa_utils.GetCommandOutput(master.primary,
144 utils.ShellQuoteArgs(cmd)))
145 _rapi_ca.flush()
146
147 port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
148 cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
149 proxy="")
150
151 if qa_config.UseVirtualCluster():
152 # TODO: Implement full support for RAPI on virtual clusters
153 print qa_logging.FormatWarning("RAPI tests are not yet supported on"
154 " virtual clusters and will be disabled")
155
156 assert _rapi_client is None
157 else:
158 _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port,
159 username=_rapi_username,
160 password=_rapi_password,
161 curl_config_fn=cfg_curl)
162
163 print "RAPI protocol version: %s" % _rapi_client.GetVersion()
164
165
166 #TODO(riba): Remove in 2.13, used just by rapi-workload which disappears there
167 def GetClient():
168 """Retrieves the RAPI client prepared by this module.
169
170 """
171 return _rapi_client
172
173
174 def _CreateRapiUser(rapi_user):
175 """RAPI credentials creation, with the secret auto-generated.
176
177 """
178 rapi_secret = utils.GenerateSecret()
179
180 master = qa_config.GetMasterNode()
181
182 rapi_users_path = qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)
183 rapi_dir = os.path.dirname(rapi_users_path)
184
185 fh = tempfile.NamedTemporaryFile()
186 try:
187 fh.write("%s %s write\n" % (rapi_user, rapi_secret))
188 fh.flush()
189
190 tmpru = qa_utils.UploadFile(master.primary, fh.name)
191 try:
192 AssertCommand(["mkdir", "-p", rapi_dir])
193 AssertCommand(["mv", tmpru, rapi_users_path])
194 finally:
195 AssertCommand(["rm", "-f", tmpru])
196 finally:
197 fh.close()
198
199 # The certificates have to be reloaded now
200 AssertCommand(["service", "ganeti", "restart"])
201
202 return rapi_secret
203
204
205 def _LookupRapiSecret(rapi_user):
206 """Find the RAPI secret for the given user on the QA machines.
207
208 @param rapi_user: Login user
209 @return: Login secret for the user
210
211 """
212 CTEXT = "{CLEARTEXT}"
213 master = qa_config.GetMasterNode()
214 cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)]
215 file_content = qa_utils.GetCommandOutput(master.primary,
216 utils.ShellQuoteArgs(cmd))
217 users = ParsePasswordFile(file_content)
218 entry = users.get(rapi_user)
219 if not entry:
220 raise qa_error.Error("User %s not found in RAPI users file" % rapi_user)
221 secret = entry.password
222 if secret.upper().startswith(CTEXT):
223 secret = secret[len(CTEXT):]
224 elif secret.startswith("{"):
225 raise qa_error.Error("Unsupported password schema for RAPI user %s:"
226 " not a clear text password" % rapi_user)
227 return secret
228
229
230 def _ReadRapiSecret(password_file_path):
231 """Reads a RAPI secret stored locally.
232
233 @type password_file_path: string
234 @return: Login secret for the user
235
236 """
237 try:
238 with open(password_file_path, 'r') as pw_file:
239 return pw_file.readline().strip()
240 except IOError:
241 raise qa_error.Error("Could not open the RAPI password file located at"
242 " %s" % password_file_path)
243
244
245 def _GetRapiSecret(rapi_user):
246 """Returns the secret to be used for RAPI access.
247
248 Where exactly this secret can be found depends on the QA configuration
249 options, and this function invokes additional tools as needed. It can
250 look up a local secret, a remote one, or create a user with a new secret.
251
252 @param rapi_user: Login user
253 @return: Login secret for the user
254
255 """
256 password_file_path = qa_config.get("rapi-password-file", None)
257 if password_file_path is not None:
258 # If the password file is specified, we use the password within.
259 # The file must be present on the QA runner.
260 return _ReadRapiSecret(password_file_path)
261 else:
262 # On an existing cluster, just find out the user's secret
263 return _LookupRapiSecret(rapi_user)
264
265
266 def SetupRapi():
267 """Sets up the RAPI certificate and usernames for the client.
268
269 """
270 if not Enabled():
271 return (None, None)
272
273 # pylint: disable=W0603
274 # due to global usage
275 global _rapi_username
276 global _rapi_password
277
278 _rapi_username = qa_config.get("rapi-user", "ganeti-qa")
279
280 if qa_config.TestEnabled("create-cluster") and \
281 qa_config.get("rapi-files-location") is None:
282 # For a new cluster, we have to invent a secret and a user, unless it has
283 # been provided separately
284 _rapi_password = _CreateRapiUser(_rapi_username)
285 else:
286 _EnsureRapiFilesPresence()
287 _rapi_password = _GetRapiSecret(_rapi_username)
288
289 # Once a username and password have been set, we can fetch the certs and
290 # get all we need for a working RAPI client.
291 ReloadCertificates(ensure_presence=False)
292
293
294 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
295 "admin_state",
296 "disk_template", "disk.sizes", "disk.spindles",
297 "nic.ips", "nic.macs", "nic.modes", "nic.links",
298 "beparams", "hvparams",
299 "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
300
301 NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
302 "mtotal", "mnode", "mfree",
303 "pinst_cnt", "sinst_cnt", "tags")
304
305 GROUP_FIELDS = compat.UniqueFrozenset([
306 "name", "uuid",
307 "alloc_policy",
308 "node_cnt", "node_list",
309 ])
310
311 JOB_FIELDS = compat.UniqueFrozenset([
312 "id", "ops", "status", "summary",
313 "opstatus", "opresult", "oplog",
314 "received_ts", "start_ts", "end_ts",
315 ])
316
317 FILTER_FIELDS = compat.UniqueFrozenset([
318 "watermark",
319 "priority",
320 "predicates",
321 "action",
322 "reason_trail",
323 "uuid",
324 ])
325
326 LIST_FIELDS = ("id", "uri")
327
328
329 def Enabled():
330 """Return whether remote API tests should be run.
331
332 """
333 # TODO: Implement RAPI tests for virtual clusters
334 return (qa_config.TestEnabled("rapi") and
335 not qa_config.UseVirtualCluster())
336
337
338 def _DoTests(uris):
339 # pylint: disable=W0212
340 # due to _SendRequest usage
341 results = []
342
343 for uri, verify, method, body in uris:
344 assert uri.startswith("/")
345
346 print "%s %s" % (method, uri)
347 data = _rapi_client._SendRequest(method, uri, None, body)
348
349 if verify is not None:
350 if callable(verify):
351 verify(data)
352 else:
353 AssertEqual(data, verify)
354
355 results.append(data)
356
357 return results
358
359
360 # pylint: disable=W0212
361 # Due to _SendRequest usage
362 def _DoGetPutTests(get_uri, modify_uri, opcode_params, rapi_only_aliases=None,
363 modify_method="PUT", exceptions=None, set_exceptions=None):
364 """ Test if all params of an object can be retrieved, and set as well.
365
366 @type get_uri: string
367 @param get_uri: The URI from which information about the object can be
368 retrieved.
369 @type modify_uri: string
370 @param modify_uri: The URI which can be used to modify the object.
371 @type opcode_params: list of tuple
372 @param opcode_params: The parameters of the underlying opcode, used to
373 determine which parameters are actually present.
374 @type rapi_only_aliases: list of string or None
375 @param rapi_only_aliases: Aliases for parameters which differ from the opcode,
376 and become renamed before opcode submission.
377 @type modify_method: string
378 @param modify_method: The method to be used in the modification.
379 @type exceptions: list of string or None
380 @param exceptions: The parameters which have not been exposed and should not
381 be tested at all.
382 @type set_exceptions: list of string or None
383 @param set_exceptions: The parameters whose setting should not be tested as a
384 part of this test.
385
386 """
387
388 assert get_uri.startswith("/")
389 assert modify_uri.startswith("/")
390
391 if exceptions is None:
392 exceptions = []
393 if set_exceptions is None:
394 set_exceptions = []
395
396 print "Testing get/modify symmetry of %s and %s" % (get_uri, modify_uri)
397
398 # First we see if all parameters of the opcode are returned through RAPI
399 params_of_interest = map(lambda x: x[0], opcode_params)
400
401 # The RAPI-specific aliases are to be checked as well
402 if rapi_only_aliases is not None:
403 params_of_interest.extend(rapi_only_aliases)
404
405 info = _rapi_client._SendRequest("GET", get_uri, None, {})
406
407 missing_params = filter(lambda x: x not in info and x not in exceptions,
408 params_of_interest)
409 if missing_params:
410 raise qa_error.Error("The parameters %s which can be set through the "
411 "appropriate opcode are not present in the response "
412 "from %s" % (','.join(missing_params), get_uri))
413
414 print "GET successful at %s" % get_uri
415
416 # Then if we can perform a set with the same values as received
417 put_payload = {}
418 for param in params_of_interest:
419 if param not in exceptions and param not in set_exceptions:
420 put_payload[param] = info[param]
421
422 _rapi_client._SendRequest(modify_method, modify_uri, None, put_payload)
423
424 print "%s successful at %s" % (modify_method, modify_uri)
425 # pylint: enable=W0212
426
427
428 def _VerifyReturnsJob(data):
429 if not isinstance(data, int):
430 AssertMatch(data, r"^\d+$")
431
432
433 def TestVersion():
434 """Testing remote API version.
435
436 """
437 _DoTests([
438 ("/version", constants.RAPI_VERSION, "GET", None),
439 ])
440
441
442 def TestEmptyCluster():
443 """Testing remote API on an empty cluster.
444
445 """
446 master = qa_config.GetMasterNode()
447 master_full = qa_utils.ResolveNodeName(master)
448
449 def _VerifyInfo(data):
450 AssertIn("name", data)
451 AssertIn("master", data)
452 AssertEqual(data["master"], master_full)
453
454 def _VerifyNodes(data):
455 master_entry = {
456 "id": master_full,
457 "uri": "/2/nodes/%s" % master_full,
458 }
459 AssertIn(master_entry, data)
460
461 def _VerifyNodesBulk(data):
462 for node in data:
463 for entry in NODE_FIELDS:
464 AssertIn(entry, node)
465
466 def _VerifyGroups(data):
467 default_group = {
468 "name": constants.INITIAL_NODE_GROUP_NAME,
469 "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
470 }
471 AssertIn(default_group, data)
472
473 def _VerifyGroupsBulk(data):
474 for group in data:
475 for field in GROUP_FIELDS:
476 AssertIn(field, group)
477
478 def _VerifyFiltersBulk(data):
479 for group in data:
480 for field in FILTER_FIELDS:
481 AssertIn(field, group)
482
483 _DoTests([
484 ("/", None, "GET", None),
485 ("/2/info", _VerifyInfo, "GET", None),
486 ("/2/tags", None, "GET", None),
487 ("/2/nodes", _VerifyNodes, "GET", None),
488 ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
489 ("/2/groups", _VerifyGroups, "GET", None),
490 ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
491 ("/2/instances", [], "GET", None),
492 ("/2/instances?bulk=1", [], "GET", None),
493 ("/2/os", None, "GET", None),
494 ("/2/filters", [], "GET", None),
495 ("/2/filters?bulk=1", _VerifyFiltersBulk, "GET", None),
496 ])
497
498 # Test HTTP Not Found
499 for method in ["GET", "PUT", "POST", "DELETE"]:
500 try:
501 _DoTests([("/99/resource/not/here/99", None, method, None)])
502 except rapi.client.GanetiApiError, err:
503 AssertEqual(err.code, 404)
504 else:
505 raise qa_error.Error("Non-existent resource didn't return HTTP 404")
506
507 # Test HTTP Not Implemented
508 for method in ["PUT", "POST", "DELETE"]:
509 try:
510 _DoTests([("/version", None, method, None)])
511 except rapi.client.GanetiApiError, err:
512 AssertEqual(err.code, 501)
513 else:
514 raise qa_error.Error("Non-implemented method didn't fail")
515
516 # Test GET/PUT symmetry
517 LEGITIMATELY_MISSING = [
518 "force", # Standard option
519 "add_uids", # Modifies UID pool, is not a param itself
520 "remove_uids", # Same as above
521 "osparams_private_cluster", # Should not be returned
522 ]
523 NOT_EXPOSED_YET = ["hv_state", "disk_state", "modify_etc_hosts"]
524 # The nicparams are returned under the default entry, yet accepted as they
525 # are - this is a TODO to fix!
526 DEFAULT_ISSUES = ["nicparams"]
527 # Cannot be set over RAPI due to security issues
528 FORBIDDEN_PARAMS = ["compression_tools"]
529
530 _DoGetPutTests("/2/info", "/2/modify", opcodes.OpClusterSetParams.OP_PARAMS,
531 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET),
532 set_exceptions=DEFAULT_ISSUES + FORBIDDEN_PARAMS)
533
534
535 def TestRapiQuery():
536 """Testing resource queries via remote API.
537
538 """
539 # FIXME: the tests are failing if no LVM is enabled, investigate
540 # if it is a bug in the QA or in the code
541 if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
542 return
543
544 master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
545 rnd = random.Random(7818)
546
547 for what in constants.QR_VIA_RAPI:
548 namefield = {
549 constants.QR_JOB: "id",
550 constants.QR_EXPORT: "export",
551 constants.QR_FILTER: "uuid",
552 }.get(what, "name")
553
554 all_fields = query.ALL_FIELDS[what].keys()
555 rnd.shuffle(all_fields)
556
557 # No fields, should return everything
558 result = _rapi_client.QueryFields(what)
559 qresult = objects.QueryFieldsResponse.FromDict(result)
560 AssertEqual(len(qresult.fields), len(all_fields))
561
562 # One field
563 result = _rapi_client.QueryFields(what, fields=[namefield])
564 qresult = objects.QueryFieldsResponse.FromDict(result)
565 AssertEqual(len(qresult.fields), 1)
566
567 # Specify all fields, order must be correct
568 result = _rapi_client.QueryFields(what, fields=all_fields)
569 qresult = objects.QueryFieldsResponse.FromDict(result)
570 AssertEqual(len(qresult.fields), len(all_fields))
571 AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
572
573 # Unknown field
574 result = _rapi_client.QueryFields(what, fields=["_unknown!"])
575 qresult = objects.QueryFieldsResponse.FromDict(result)
576 AssertEqual(len(qresult.fields), 1)
577 AssertEqual(qresult.fields[0].name, "_unknown!")
578 AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
579
580 # Try once more, this time without the client
581 _DoTests([
582 ("/2/query/%s/fields" % what, None, "GET", None),
583 ("/2/query/%s/fields?fields=%s,%s,%s" % (what, namefield, namefield,
584 all_fields[0]),
585 None, "GET", None),
586 ])
587
588 # Try missing query argument
589 try:
590 _DoTests([
591 ("/2/query/%s" % what, None, "GET", None),
592 ])
593 except rapi.client.GanetiApiError, err:
594 AssertEqual(err.code, 400)
595 else:
596 raise qa_error.Error("Request missing 'fields' parameter didn't fail")
597
598 def _Check(exp_fields, data):
599 qresult = objects.QueryResponse.FromDict(data)
600 AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
601 if not isinstance(qresult.data, list):
602 raise qa_error.Error("Query did not return a list")
603
604 _DoTests([
605 # Specify fields in query
606 ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
607 compat.partial(_Check, all_fields), "GET", None),
608
609 ("/2/query/%s?fields=%s" % (what, namefield),
610 compat.partial(_Check, [namefield]), "GET", None),
611
612 # Note the spaces
613 ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
614 (what, namefield, namefield, namefield),
615 compat.partial(_Check, [namefield] * 3), "GET", None)])
616
617 if what in constants.QR_VIA_RAPI_PUT:
618 _DoTests([
619 # PUT with fields in query
620 ("/2/query/%s?fields=%s" % (what, namefield),
621 compat.partial(_Check, [namefield]), "PUT", {}),
622
623 ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
624 "fields": [namefield] * 4,
625 }),
626
627 ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
628 "fields": all_fields,
629 }),
630
631 ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
632 "fields": [namefield] * 4
633 })])
634
635 if what in constants.QR_VIA_RAPI_PUT:
636 trivial_filter = {
637 constants.QR_JOB: [qlang.OP_GE, namefield, 0],
638 }.get(what, [qlang.OP_REGEXP, namefield, ".*"])
639
640 _DoTests([
641 # With filter
642 ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
643 "fields": all_fields,
644 "filter": trivial_filter
645 }),
646 ])
647
648 if what == constants.QR_NODE:
649 # Test with filter
650 (nodes, ) = _DoTests(
651 [("/2/query/%s" % what,
652 compat.partial(_Check, ["name", "master"]), "PUT",
653 {"fields": ["name", "master"],
654 "filter": [qlang.OP_TRUE, "master"],
655 })])
656 qresult = objects.QueryResponse.FromDict(nodes)
657 AssertEqual(qresult.data, [
658 [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
659 ])
660
661
662 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
663 def TestInstance(instance):
664 """Testing getting instance(s) info via remote API.
665
666 """
667 def _VerifyInstance(data):
668 for entry in INSTANCE_FIELDS:
669 AssertIn(entry, data)
670
671 def _VerifyInstancesList(data):
672 for instance in data:
673 for entry in LIST_FIELDS:
674 AssertIn(entry, instance)
675
676 def _VerifyInstancesBulk(data):
677 for instance_data in data:
678 _VerifyInstance(instance_data)
679
680 _DoTests([
681 ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
682 ("/2/instances", _VerifyInstancesList, "GET", None),
683 ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
684 ("/2/instances/%s/activate-disks" % instance.name,
685 _VerifyReturnsJob, "PUT", None),
686 ("/2/instances/%s/deactivate-disks" % instance.name,
687 _VerifyReturnsJob, "PUT", None),
688 ])
689
690 # Test OpBackupPrepare
691 (job_id, ) = _DoTests([
692 ("/2/instances/%s/prepare-export?mode=%s" %
693 (instance.name, constants.EXPORT_MODE_REMOTE),
694 _VerifyReturnsJob, "PUT", None),
695 ])
696
697 result = _WaitForRapiJob(job_id)[0]
698 AssertEqual(len(result["handshake"]), 3)
699 AssertEqual(result["handshake"][0], constants.RIE_VERSION)
700 AssertEqual(len(result["x509_key_name"]), 3)
701 AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
702
703
704 def TestNode(node):
705 """Testing getting node(s) info via remote API.
706
707 """
708 def _VerifyNode(data):
709 for entry in NODE_FIELDS:
710 AssertIn(entry, data)
711
712 def _VerifyNodesList(data):
713 for node in data:
714 for entry in LIST_FIELDS:
715 AssertIn(entry, node)
716
717 def _VerifyNodesBulk(data):
718 for node_data in data:
719 _VerifyNode(node_data)
720
721 _DoTests([
722 ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
723 ("/2/nodes", _VerifyNodesList, "GET", None),
724 ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
725 ])
726
727 # Not parameters of the node, but controlling opcode behavior
728 LEGITIMATELY_MISSING = ["force", "powered"]
729 # Identifying the node - RAPI provides these itself
730 IDENTIFIERS = ["node_name", "node_uuid"]
731 # As the name states, these can be set but not retrieved yet
732 NOT_EXPOSED_YET = ["hv_state", "disk_state", "auto_promote"]
733
734 _DoGetPutTests("/2/nodes/%s" % node.primary,
735 "/2/nodes/%s/modify" % node.primary,
736 opcodes.OpNodeSetParams.OP_PARAMS,
737 modify_method="POST",
738 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET +
739 IDENTIFIERS))
740
741
742 def _FilterTags(seq):
743 """Removes unwanted tags from a sequence.
744
745 """
746 ignore_re = qa_config.get("ignore-tags-re", None)
747
748 if ignore_re:
749 return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
750 else:
751 return seq
752
753
754 def TestTags(kind, name, tags):
755 """Tests .../tags resources.
756
757 """
758 if kind == constants.TAG_CLUSTER:
759 uri = "/2/tags"
760 elif kind == constants.TAG_NODE:
761 uri = "/2/nodes/%s/tags" % name
762 elif kind == constants.TAG_INSTANCE:
763 uri = "/2/instances/%s/tags" % name
764 elif kind == constants.TAG_NODEGROUP:
765 uri = "/2/groups/%s/tags" % name
766 elif kind == constants.TAG_NETWORK:
767 uri = "/2/networks/%s/tags" % name
768 else:
769 raise errors.ProgrammerError("Unknown tag kind")
770
771 def _VerifyTags(data):
772 AssertEqual(sorted(tags), sorted(_FilterTags(data)))
773
774 queryargs = "&".join("tag=%s" % i for i in tags)
775
776 # Add tags
777 (job_id, ) = _DoTests([
778 ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
779 ])
780 _WaitForRapiJob(job_id)
781
782 # Retrieve tags
783 _DoTests([
784 (uri, _VerifyTags, "GET", None),
785 ])
786
787 # Remove tags
788 (job_id, ) = _DoTests([
789 ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
790 ])
791 _WaitForRapiJob(job_id)
792
793
794 def _WaitForRapiJob(job_id):
795 """Waits for a job to finish.
796
797 """
798 def _VerifyJob(data):
799 AssertEqual(data["id"], job_id)
800 for field in JOB_FIELDS:
801 AssertIn(field, data)
802
803 _DoTests([
804 ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
805 ])
806
807 return rapi.client_utils.PollJob(_rapi_client, job_id,
808 cli.StdioJobPollReportCb())
809
810
811 def TestRapiNodeGroups():
812 """Test several node group operations using RAPI.
813
814 """
815 (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
816
817 # Create a group with no attributes
818 body = {
819 "name": group1,
820 }
821
822 (job_id, ) = _DoTests([
823 ("/2/groups", _VerifyReturnsJob, "POST", body),
824 ])
825
826 _WaitForRapiJob(job_id)
827
828 # Create a group specifying alloc_policy
829 body = {
830 "name": group2,
831 "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
832 }
833
834 (job_id, ) = _DoTests([
835 ("/2/groups", _VerifyReturnsJob, "POST", body),
836 ])
837
838 _WaitForRapiJob(job_id)
839
840 # Modify alloc_policy
841 body = {
842 "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
843 }
844
845 (job_id, ) = _DoTests([
846 ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
847 ])
848
849 _WaitForRapiJob(job_id)
850
851 # Rename a group
852 body = {
853 "new_name": group3,
854 }
855
856 (job_id, ) = _DoTests([
857 ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
858 ])
859
860 _WaitForRapiJob(job_id)
861
862 # Test for get/set symmetry
863
864 # Identifying the node - RAPI provides these itself
865 IDENTIFIERS = ["group_name"]
866 # As the name states, not exposed yet
867 NOT_EXPOSED_YET = ["hv_state", "disk_state"]
868
869 # The parameters we do not want to get and set (as that sets the
870 # group-specific params to the filled ones)
871 FILLED_PARAMS = ["ndparams", "ipolicy", "diskparams"]
872
873 # The aliases that we can use to perform this test with the group-specific
874 # params
875 CUSTOM_PARAMS = ["custom_ndparams", "custom_ipolicy", "custom_diskparams"]
876
877 _DoGetPutTests("/2/groups/%s" % group3, "/2/groups/%s/modify" % group3,
878 opcodes.OpGroupSetParams.OP_PARAMS,
879 rapi_only_aliases=CUSTOM_PARAMS,
880 exceptions=(IDENTIFIERS + NOT_EXPOSED_YET),
881 set_exceptions=FILLED_PARAMS)
882
883 # Delete groups
884 for group in [group1, group3]:
885 (job_id, ) = _DoTests([
886 ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
887 ])
888
889 _WaitForRapiJob(job_id)
890
891
892 def TestRapiInstanceAdd(node, use_client):
893 """Test adding a new instance via RAPI"""
894 if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
895 return
896 instance = qa_config.AcquireInstance()
897 instance.SetDiskTemplate(constants.DT_PLAIN)
898 try:
899 disks = [{"size": utils.ParseUnit(d.get("size")),
900 "name": str(d.get("name"))}
901 for d in qa_config.GetDiskOptions()]
902 nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
903 nics = [{
904 constants.INIC_MAC: nic0_mac,
905 }]
906
907 beparams = {
908 constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
909 constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
910 }
911
912 if use_client:
913 job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
914 instance.name,
915 constants.DT_PLAIN,
916 disks, nics,
917 os=qa_config.get("os"),
918 pnode=node.primary,
919 beparams=beparams)
920 else:
921 body = {
922 "__version__": 1,
923 "mode": constants.INSTANCE_CREATE,
924 "name": instance.name,
925 "os_type": qa_config.get("os"),
926 "disk_template": constants.DT_PLAIN,
927 "pnode": node.primary,
928 "beparams": beparams,
929 "disks": disks,
930 "nics": nics,
931 }
932
933 (job_id, ) = _DoTests([
934 ("/2/instances", _VerifyReturnsJob, "POST", body),
935 ])
936
937 _WaitForRapiJob(job_id)
938
939 return instance
940 except:
941 instance.Release()
942 raise
943
944
945 def _GenInstanceAllocationDict(node, instance):
946 """Creates an instance allocation dict to be used with the RAPI"""
947 instance.SetDiskTemplate(constants.DT_PLAIN)
948
949 disks = [{"size": utils.ParseUnit(d.get("size")),
950 "name": str(d.get("name"))}
951 for d in qa_config.GetDiskOptions()]
952
953 nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
954 nics = [{
955 constants.INIC_MAC: nic0_mac,
956 }]
957
958 beparams = {
959 constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
960 constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
961 }
962
963 return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
964 instance.name,
965 constants.DT_PLAIN,
966 disks, nics,
967 os=qa_config.get("os"),
968 pnode=node.primary,
969 beparams=beparams)
970
971
972 def TestRapiInstanceMultiAlloc(node):
973 """Test adding two new instances via the RAPI instance-multi-alloc method"""
974 if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
975 return
976
977 JOBS_KEY = "jobs"
978
979 instance_one = qa_config.AcquireInstance()
980 instance_two = qa_config.AcquireInstance()
981 instance_list = [instance_one, instance_two]
982 try:
983 rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
984 instance_list)
985
986 job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
987
988 results, = _WaitForRapiJob(job_id)
989
990 if JOBS_KEY not in results:
991 raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
992 "information about created jobs")
993
994 if len(results[JOBS_KEY]) != len(instance_list):
995 raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
996 "desired number of jobs!")
997
998 for success, job in results[JOBS_KEY]:
999 if success:
1000 _WaitForRapiJob(job)
1001 else:
1002 raise qa_error.Error("Failed to create instance in "
1003 "instance-multi-alloc call")
1004 except:
1005 # Note that although released, it may be that some of the instance creations
1006 # have in fact succeeded. Handling this in a better way may be possible, but
1007 # is not necessary as the QA has already failed at this point.
1008 for instance in instance_list:
1009 instance.Release()
1010 raise
1011
1012 return (instance_one, instance_two)
1013
1014
1015 @InstanceCheck(None, INST_DOWN, FIRST_ARG)
1016 def TestRapiInstanceRemove(instance, use_client):
1017 """Test removing instance via RAPI"""
1018 # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
1019 # in RAPI or in the test
1020 if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
1021 return
1022
1023 if use_client:
1024 job_id = _rapi_client.DeleteInstance(instance.name)
1025 else:
1026 (job_id, ) = _DoTests([
1027 ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
1028 ])
1029
1030 _WaitForRapiJob(job_id)
1031
1032
1033 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1034 def TestRapiInstanceMigrate(instance):
1035 """Test migrating instance via RAPI"""
1036 if not IsMigrationSupported(instance):
1037 print qa_logging.FormatInfo("Instance doesn't support migration, skipping"
1038 " test")
1039 return
1040 # Move to secondary node
1041 _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
1042 qa_utils.RunInstanceCheck(instance, True)
1043 # And back to previous primary
1044 _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
1045
1046
1047 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1048 def TestRapiInstanceFailover(instance):
1049 """Test failing over instance via RAPI"""
1050 if not IsFailoverSupported(instance):
1051 print qa_logging.FormatInfo("Instance doesn't support failover, skipping"
1052 " test")
1053 return
1054 # Move to secondary node
1055 _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
1056 qa_utils.RunInstanceCheck(instance, True)
1057 # And back to previous primary
1058 _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
1059
1060
1061 @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
1062 def TestRapiInstanceShutdown(instance):
1063 """Test stopping an instance via RAPI"""
1064 _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
1065
1066
1067 @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
1068 def TestRapiInstanceStartup(instance):
1069 """Test starting an instance via RAPI"""
1070 _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
1071
1072
1073 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
1074 def TestRapiInstanceRenameAndBack(rename_source, rename_target):
1075 """Test renaming instance via RAPI
1076
1077 This must leave the instance with the original name (in the
1078 non-failure case).
1079
1080 """
1081 _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
1082 qa_utils.RunInstanceCheck(rename_source, False)
1083 qa_utils.RunInstanceCheck(rename_target, False)
1084 _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
1085 qa_utils.RunInstanceCheck(rename_target, False)
1086
1087
1088 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
1089 def TestRapiInstanceReinstall(instance):
1090 """Test reinstalling an instance via RAPI"""
1091 if instance.disk_template == constants.DT_DISKLESS:
1092 print qa_logging.FormatInfo("Test not supported for diskless instances")
1093 return
1094
1095 _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
1096 # By default, the instance is started again
1097 qa_utils.RunInstanceCheck(instance, True)
1098
1099 # Reinstall again without starting
1100 _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
1101 no_startup=True))
1102
1103
1104 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1105 def TestRapiInstanceReplaceDisks(instance):
1106 """Test replacing instance disks via RAPI"""
1107 if not IsDiskReplacingSupported(instance):
1108 print qa_logging.FormatInfo("Instance doesn't support disk replacing,"
1109 " skipping test")
1110 return
1111 fn = _rapi_client.ReplaceInstanceDisks
1112 _WaitForRapiJob(fn(instance.name,
1113 mode=constants.REPLACE_DISK_AUTO, disks=[]))
1114 _WaitForRapiJob(fn(instance.name,
1115 mode=constants.REPLACE_DISK_SEC, disks="0"))
1116
1117
1118 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1119 def TestRapiInstanceModify(instance):
1120 """Test modifying instance via RAPI"""
1121 default_hv = qa_config.GetDefaultHypervisor()
1122
1123 def _ModifyInstance(**kwargs):
1124 _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
1125
1126 _ModifyInstance(beparams={
1127 constants.BE_VCPUS: 3,
1128 })
1129
1130 _ModifyInstance(beparams={
1131 constants.BE_VCPUS: constants.VALUE_DEFAULT,
1132 })
1133
1134 if default_hv == constants.HT_XEN_PVM:
1135 _ModifyInstance(hvparams={
1136 constants.HV_KERNEL_ARGS: "single",
1137 })
1138 _ModifyInstance(hvparams={
1139 constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
1140 })
1141 elif default_hv == constants.HT_XEN_HVM:
1142 _ModifyInstance(hvparams={
1143 constants.HV_BOOT_ORDER: "acn",
1144 })
1145 _ModifyInstance(hvparams={
1146 constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
1147 })
1148
1149
1150 @InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1151 def TestRapiInstanceConsole(instance):
1152 """Test getting instance console information via RAPI"""
1153 result = _rapi_client.GetInstanceConsole(instance.name)
1154 console = objects.InstanceConsole.FromDict(result)
1155 AssertEqual(console.Validate(), None)
1156 AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
1157
1158
1159 @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
1160 def TestRapiStoppedInstanceConsole(instance):
1161 """Test getting stopped instance's console information via RAPI"""
1162 try:
1163 _rapi_client.GetInstanceConsole(instance.name)
1164 except rapi.client.GanetiApiError, err:
1165 AssertEqual(err.code, 503)
1166 else:
1167 raise qa_error.Error("Getting console for stopped instance didn't"
1168 " return HTTP 503")
1169
1170
1171 def GetOperatingSystems():
1172 """Retrieves a list of all available operating systems.
1173
1174 """
1175 return _rapi_client.GetOperatingSystems()
1176
1177
1178 def _InvokeMoveInstance(current_dest_inst, current_src_inst, rapi_pw_filename,
1179 joint_master, perform_checks, target_nodes=None):
1180 """ Invokes the move-instance tool for testing purposes.
1181
1182 """
1183 # Some uses of this test might require that RAPI-only commands are used,
1184 # and the checks are command-line based.
1185 if perform_checks:
1186 qa_utils.RunInstanceCheck(current_dest_inst, False)
1187
1188 cmd = [
1189 "../tools/move-instance",
1190 "--verbose",
1191 "--src-ca-file=%s" % _rapi_ca.name,
1192 "--src-username=%s" % _rapi_username,
1193 "--src-password-file=%s" % rapi_pw_filename,
1194 "--dest-instance-name=%s" % current_dest_inst,
1195 ]
1196
1197 if target_nodes:
1198 pnode, snode = target_nodes
1199 cmd.extend([
1200 "--dest-primary-node=%s" % pnode,
1201 "--dest-secondary-node=%s" % snode,
1202 ])
1203 else:
1204 cmd.extend([
1205 "--iallocator=%s" % constants.IALLOC_HAIL,
1206 "--opportunistic-tries=1",
1207 ])
1208
1209 cmd.extend([
1210 "--net=0:mac=%s" % constants.VALUE_GENERATE,
1211 joint_master,
1212 joint_master,
1213 current_src_inst,
1214 ])
1215
1216 AssertEqual(StartLocalCommand(cmd).wait(), 0)
1217
1218 if perform_checks:
1219 qa_utils.RunInstanceCheck(current_src_inst, False)
1220 qa_utils.RunInstanceCheck(current_dest_inst, True)
1221
1222
1223 def TestInterClusterInstanceMove(src_instance, dest_instance,
1224 inodes, tnode, perform_checks=True):
1225 """Test tools/move-instance"""
1226 master = qa_config.GetMasterNode()
1227
1228 rapi_pw_file = tempfile.NamedTemporaryFile()
1229 rapi_pw_file.write(_rapi_password)
1230 rapi_pw_file.flush()
1231
1232 # Needed only if checks are to be performed
1233 if perform_checks:
1234 dest_instance.SetDiskTemplate(src_instance.disk_template)
1235
1236 # TODO: Run some instance tests before moving back
1237
1238 if len(inodes) > 1:
1239 # No disk template currently requires more than 1 secondary node. If this
1240 # changes, either this test must be skipped or the script must be updated.
1241 assert len(inodes) == 2
1242 snode = inodes[1]
1243 else:
1244 # Instance is not redundant, but we still need to pass a node
1245 # (which will be ignored)
1246 snode = tnode
1247 pnode = inodes[0]
1248
1249 # pnode:snode are the *current* nodes, and the first move is an
1250 # iallocator-guided move outside of pnode. The node lock for the pnode
1251 # assures that this happens, and while we cannot be sure where the instance
1252 # will land, it is a real move.
1253 locks = {locking.LEVEL_NODE: [pnode.primary]}
1254 RunWithLocks(_InvokeMoveInstance, locks, 600.0, False,
1255 dest_instance.name, src_instance.name, rapi_pw_file.name,
1256 master.primary, perform_checks)
1257
1258 # And then back to pnode:snode
1259 _InvokeMoveInstance(src_instance.name, dest_instance.name, rapi_pw_file.name,
1260 master.primary, perform_checks,
1261 target_nodes=(pnode.primary, snode.primary))
1262
1263
1264 def TestFilters():
1265 """Testing filter management via the remote API.
1266
1267 """
1268
1269 body = {
1270 "priority": 10,
1271 "predicates": [],
1272 "action": "CONTINUE",
1273 "reason": [(constants.OPCODE_REASON_SRC_USER,
1274 "reason1",
1275 utils.EpochNano())],
1276 }
1277
1278 body1 = copy.deepcopy(body)
1279 body1["priority"] = 20
1280
1281 # Query filters
1282 _DoTests([("/2/filters", [], "GET", None)])
1283
1284 # Add a filter via POST and delete it again
1285 uuid = _DoTests([("/2/filters", None, "POST", body)])[0]
1286 uuid_module.UUID(uuid) # Check if uuid is a valid UUID
1287 _DoTests([("/2/filters/%s" % uuid, lambda r: r is None, "DELETE", None)])
1288
1289 _DoTests([
1290 # Check PUT-inserting a nonexistent filter with given UUID
1291 ("/2/filters/%s" % uuid, lambda u: u == uuid, "PUT", body),
1292 # Check PUT-inserting an existent filter with given UUID
1293 ("/2/filters/%s" % uuid, lambda u: u == uuid, "PUT", body1),
1294 # Check that the update changed the filter
1295 ("/2/filters/%s" % uuid, lambda f: f["priority"] == 20, "GET", None),
1296 # Delete it again
1297 ("/2/filters/%s" % uuid, lambda r: r is None, "DELETE", None),
1298 ])
1299
1300 # Add multiple filters, query and delete them
1301 uuids = _DoTests([
1302 ("/2/filters", None, "POST", body),
1303 ("/2/filters", None, "POST", body),
1304 ("/2/filters", None, "POST", body),
1305 ])
1306 _DoTests([("/2/filters", lambda rs: [r["uuid"] for r in rs] == uuids,
1307 "GET", None)])
1308 for u in uuids:
1309 _DoTests([("/2/filters/%s" % u, lambda r: r is None, "DELETE", None)])
1310
1311
1312 _DRBD_SECRET_RE = re.compile('shared-secret.*"([0-9A-Fa-f]+)"')
1313
1314
1315 def _RetrieveSecret(instance, pnode):
1316 """Retrieves the DRBD secret given an instance object and the primary node.
1317
1318 @type instance: L{qa_config._QaInstance}
1319 @type pnode: L{qa_config._QaNode}
1320
1321 @rtype: string
1322
1323 """
1324 instance_info = GetInstanceInfo(instance.name)
1325
1326 # We are interested in only the first disk on the primary
1327 drbd_minor = instance_info["drbd-minors"][pnode.primary][0]
1328
1329 # This form should work for all DRBD versions
1330 drbd_command = ("drbdsetup show %d; drbdsetup %d show || true" %
1331 (drbd_minor, drbd_minor))
1332 instance_drbd_info = \
1333 qa_utils.GetCommandOutput(pnode.primary, drbd_command)
1334
1335 match_obj = _DRBD_SECRET_RE.search(instance_drbd_info)
1336 if match_obj is None:
1337 raise qa_error.Error("Could not retrieve DRBD secret for instance %s from"
1338 " node %s." % (instance.name, pnode.primary))
1339
1340 return match_obj.groups(0)[0]
1341
1342
1343 def TestInstanceDataCensorship(instance, inodes):
1344 """Test protection of sensitive instance data."""
1345
1346 if instance.disk_template != constants.DT_DRBD8:
1347 print qa_utils.FormatInfo("Only the DRBD secret is a sensitive parameter"
1348 " right now, skipping for non-DRBD instance.")
1349 return
1350
1351 drbd_secret = _RetrieveSecret(instance, inodes[0])
1352
1353 job_id = _rapi_client.GetInstanceInfo(instance.name)
1354 if not _rapi_client.WaitForJobCompletion(job_id):
1355 raise qa_error.Error("Could not fetch instance info for instance %s" %
1356 instance.name)
1357 info_dict = _rapi_client.GetJobStatus(job_id)
1358
1359 if drbd_secret in str(info_dict):
1360 print qa_utils.FormatInfo("DRBD secret: %s" % drbd_secret)
1361 print qa_utils.FormatInfo("Retrieved data\n%s" % str(info_dict))
1362 raise qa_error.Error("Found DRBD secret in contents of RAPI instance info"
1363 " call; see above.")