| # |
| # |
| |
| # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program; if not, write to the Free Software |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| # 02110-1301, USA. |
| |
| |
| """Remote API QA tests. |
| |
| """ |
| |
| import tempfile |
| import random |
| import re |
| import itertools |
| import functools |
| |
| from ganeti import utils |
| from ganeti import constants |
| from ganeti import errors |
| from ganeti import cli |
| from ganeti import rapi |
| from ganeti import objects |
| from ganeti import query |
| from ganeti import compat |
| from ganeti import qlang |
| from ganeti import pathutils |
| |
| import ganeti.rapi.client # pylint: disable=W0611 |
| import ganeti.rapi.client_utils |
| |
| import qa_config |
| import qa_error |
| import qa_logging |
| import qa_utils |
| |
| from qa_instance import GetInstanceInfo |
| from qa_instance import IsFailoverSupported |
| from qa_instance import IsMigrationSupported |
| from qa_instance import IsDiskReplacingSupported |
| from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand) |
| from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG |
| |
| |
| _rapi_ca = None |
| _rapi_client = None |
| _rapi_username = None |
| _rapi_password = None |
| |
| |
| def Setup(username, password): |
| """Configures the RAPI client. |
| |
| """ |
| # pylint: disable=W0603 |
| # due to global usage |
| global _rapi_ca |
| global _rapi_client |
| global _rapi_username |
| global _rapi_password |
| |
| _rapi_username = username |
| _rapi_password = password |
| |
| master = qa_config.GetMasterNode() |
| |
| # Load RAPI certificate from master node |
| cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_CERT_FILE)] |
| |
| # Write to temporary file |
| _rapi_ca = tempfile.NamedTemporaryFile() |
| _rapi_ca.write(qa_utils.GetCommandOutput(master.primary, |
| utils.ShellQuoteArgs(cmd))) |
| _rapi_ca.flush() |
| |
| port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT) |
| cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name, |
| proxy="") |
| |
| if qa_config.UseVirtualCluster(): |
| # TODO: Implement full support for RAPI on virtual clusters |
| print qa_logging.FormatWarning("RAPI tests are not yet supported on" |
| " virtual clusters and will be disabled") |
| |
| assert _rapi_client is None |
| else: |
| _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port, |
| username=username, |
| password=password, |
| curl_config_fn=cfg_curl) |
| |
| print "RAPI protocol version: %s" % _rapi_client.GetVersion() |
| |
| |
| INSTANCE_FIELDS = ("name", "os", "pnode", "snodes", |
| "admin_state", |
| "disk_template", "disk.sizes", "disk.spindles", |
| "nic.ips", "nic.macs", "nic.modes", "nic.links", |
| "beparams", "hvparams", |
| "oper_state", "oper_ram", "oper_vcpus", "status", "tags") |
| |
| NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree", |
| "mtotal", "mnode", "mfree", |
| "pinst_cnt", "sinst_cnt", "tags") |
| |
| GROUP_FIELDS = compat.UniqueFrozenset([ |
| "name", "uuid", |
| "alloc_policy", |
| "node_cnt", "node_list", |
| ]) |
| |
| JOB_FIELDS = compat.UniqueFrozenset([ |
| "id", "ops", "status", "summary", |
| "opstatus", "opresult", "oplog", |
| "received_ts", "start_ts", "end_ts", |
| ]) |
| |
| LIST_FIELDS = ("id", "uri") |
| |
| |
| def Enabled(): |
| """Return whether remote API tests should be run. |
| |
| """ |
| # TODO: Implement RAPI tests for virtual clusters |
| return (qa_config.TestEnabled("rapi") and |
| not qa_config.UseVirtualCluster()) |
| |
| |
| def _DoTests(uris): |
| # pylint: disable=W0212 |
| # due to _SendRequest usage |
| results = [] |
| |
| for uri, verify, method, body in uris: |
| assert uri.startswith("/") |
| |
| print "%s %s" % (method, uri) |
| data = _rapi_client._SendRequest(method, uri, None, body) |
| |
| if verify is not None: |
| if callable(verify): |
| verify(data) |
| else: |
| AssertEqual(data, verify) |
| |
| results.append(data) |
| |
| return results |
| |
| |
| def _VerifyReturnsJob(data): |
| if not isinstance(data, int): |
| AssertMatch(data, r"^\d+$") |
| |
| |
| def TestVersion(): |
| """Testing remote API version. |
| |
| """ |
| _DoTests([ |
| ("/version", constants.RAPI_VERSION, "GET", None), |
| ]) |
| |
| |
| def TestEmptyCluster(): |
| """Testing remote API on an empty cluster. |
| |
| """ |
| master = qa_config.GetMasterNode() |
| master_full = qa_utils.ResolveNodeName(master) |
| |
| def _VerifyInfo(data): |
| AssertIn("name", data) |
| AssertIn("master", data) |
| AssertEqual(data["master"], master_full) |
| |
| def _VerifyNodes(data): |
| master_entry = { |
| "id": master_full, |
| "uri": "/2/nodes/%s" % master_full, |
| } |
| AssertIn(master_entry, data) |
| |
| def _VerifyNodesBulk(data): |
| for node in data: |
| for entry in NODE_FIELDS: |
| AssertIn(entry, node) |
| |
| def _VerifyGroups(data): |
| default_group = { |
| "name": constants.INITIAL_NODE_GROUP_NAME, |
| "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME, |
| } |
| AssertIn(default_group, data) |
| |
| def _VerifyGroupsBulk(data): |
| for group in data: |
| for field in GROUP_FIELDS: |
| AssertIn(field, group) |
| |
| _DoTests([ |
| ("/", None, "GET", None), |
| ("/2/info", _VerifyInfo, "GET", None), |
| ("/2/tags", None, "GET", None), |
| ("/2/nodes", _VerifyNodes, "GET", None), |
| ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None), |
| ("/2/groups", _VerifyGroups, "GET", None), |
| ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None), |
| ("/2/instances", [], "GET", None), |
| ("/2/instances?bulk=1", [], "GET", None), |
| ("/2/os", None, "GET", None), |
| ]) |
| |
| # Test HTTP Not Found |
| for method in ["GET", "PUT", "POST", "DELETE"]: |
| try: |
| _DoTests([("/99/resource/not/here/99", None, method, None)]) |
| except rapi.client.GanetiApiError, err: |
| AssertEqual(err.code, 404) |
| else: |
| raise qa_error.Error("Non-existent resource didn't return HTTP 404") |
| |
| # Test HTTP Not Implemented |
| for method in ["PUT", "POST", "DELETE"]: |
| try: |
| _DoTests([("/version", None, method, None)]) |
| except rapi.client.GanetiApiError, err: |
| AssertEqual(err.code, 501) |
| else: |
| raise qa_error.Error("Non-implemented method didn't fail") |
| |
| |
| def TestRapiQuery(): |
| """Testing resource queries via remote API. |
| |
| """ |
| # FIXME: the tests are failing if no LVM is enabled, investigate |
| # if it is a bug in the QA or in the code |
| if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG): |
| return |
| |
| master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode()) |
| rnd = random.Random(7818) |
| |
| for what in constants.QR_VIA_RAPI: |
| if what == constants.QR_JOB: |
| namefield = "id" |
| elif what == constants.QR_EXPORT: |
| namefield = "export" |
| else: |
| namefield = "name" |
| |
| all_fields = query.ALL_FIELDS[what].keys() |
| rnd.shuffle(all_fields) |
| |
| # No fields, should return everything |
| result = _rapi_client.QueryFields(what) |
| qresult = objects.QueryFieldsResponse.FromDict(result) |
| AssertEqual(len(qresult.fields), len(all_fields)) |
| |
| # One field |
| result = _rapi_client.QueryFields(what, fields=[namefield]) |
| qresult = objects.QueryFieldsResponse.FromDict(result) |
| AssertEqual(len(qresult.fields), 1) |
| |
| # Specify all fields, order must be correct |
| result = _rapi_client.QueryFields(what, fields=all_fields) |
| qresult = objects.QueryFieldsResponse.FromDict(result) |
| AssertEqual(len(qresult.fields), len(all_fields)) |
| AssertEqual([fdef.name for fdef in qresult.fields], all_fields) |
| |
| # Unknown field |
| result = _rapi_client.QueryFields(what, fields=["_unknown!"]) |
| qresult = objects.QueryFieldsResponse.FromDict(result) |
| AssertEqual(len(qresult.fields), 1) |
| AssertEqual(qresult.fields[0].name, "_unknown!") |
| AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN) |
| |
| # Try once more, this time without the client |
| _DoTests([ |
| ("/2/query/%s/fields" % what, None, "GET", None), |
| ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]), |
| None, "GET", None), |
| ]) |
| |
| # Try missing query argument |
| try: |
| _DoTests([ |
| ("/2/query/%s" % what, None, "GET", None), |
| ]) |
| except rapi.client.GanetiApiError, err: |
| AssertEqual(err.code, 400) |
| else: |
| raise qa_error.Error("Request missing 'fields' parameter didn't fail") |
| |
| def _Check(exp_fields, data): |
| qresult = objects.QueryResponse.FromDict(data) |
| AssertEqual([fdef.name for fdef in qresult.fields], exp_fields) |
| if not isinstance(qresult.data, list): |
| raise qa_error.Error("Query did not return a list") |
| |
| _DoTests([ |
| # Specify fields in query |
| ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)), |
| compat.partial(_Check, all_fields), "GET", None), |
| |
| ("/2/query/%s?fields=%s" % (what, namefield), |
| compat.partial(_Check, [namefield]), "GET", None), |
| |
| # Note the spaces |
| ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" % |
| (what, namefield, namefield, namefield), |
| compat.partial(_Check, [namefield] * 3), "GET", None), |
| |
| # PUT with fields in query |
| ("/2/query/%s?fields=%s" % (what, namefield), |
| compat.partial(_Check, [namefield]), "PUT", {}), |
| |
| ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", { |
| "fields": [namefield] * 4, |
| }), |
| |
| ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", { |
| "fields": all_fields, |
| }), |
| |
| ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", { |
| "fields": [namefield] * 4 |
| })]) |
| |
| def _CheckFilter(): |
| _DoTests([ |
| # With filter |
| ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", { |
| "fields": all_fields, |
| "filter": [qlang.OP_TRUE, namefield], |
| }), |
| ]) |
| |
| if what == constants.QR_LOCK: |
| # Locks can't be filtered |
| try: |
| _CheckFilter() |
| except rapi.client.GanetiApiError, err: |
| AssertEqual(err.code, 500) |
| else: |
| raise qa_error.Error("Filtering locks didn't fail") |
| else: |
| _CheckFilter() |
| |
| if what == constants.QR_NODE: |
| # Test with filter |
| (nodes, ) = _DoTests( |
| [("/2/query/%s" % what, |
| compat.partial(_Check, ["name", "master"]), "PUT", |
| {"fields": ["name", "master"], |
| "filter": [qlang.OP_TRUE, "master"], |
| })]) |
| qresult = objects.QueryResponse.FromDict(nodes) |
| AssertEqual(qresult.data, [ |
| [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]], |
| ]) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestInstance(instance): |
| """Testing getting instance(s) info via remote API. |
| |
| """ |
| def _VerifyInstance(data): |
| for entry in INSTANCE_FIELDS: |
| AssertIn(entry, data) |
| |
| def _VerifyInstancesList(data): |
| for instance in data: |
| for entry in LIST_FIELDS: |
| AssertIn(entry, instance) |
| |
| def _VerifyInstancesBulk(data): |
| for instance_data in data: |
| _VerifyInstance(instance_data) |
| |
| _DoTests([ |
| ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None), |
| ("/2/instances", _VerifyInstancesList, "GET", None), |
| ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None), |
| ("/2/instances/%s/activate-disks" % instance.name, |
| _VerifyReturnsJob, "PUT", None), |
| ("/2/instances/%s/deactivate-disks" % instance.name, |
| _VerifyReturnsJob, "PUT", None), |
| ]) |
| |
| # Test OpBackupPrepare |
| (job_id, ) = _DoTests([ |
| ("/2/instances/%s/prepare-export?mode=%s" % |
| (instance.name, constants.EXPORT_MODE_REMOTE), |
| _VerifyReturnsJob, "PUT", None), |
| ]) |
| |
| result = _WaitForRapiJob(job_id)[0] |
| AssertEqual(len(result["handshake"]), 3) |
| AssertEqual(result["handshake"][0], constants.RIE_VERSION) |
| AssertEqual(len(result["x509_key_name"]), 3) |
| AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"]) |
| |
| |
| def TestNode(node): |
| """Testing getting node(s) info via remote API. |
| |
| """ |
| def _VerifyNode(data): |
| for entry in NODE_FIELDS: |
| AssertIn(entry, data) |
| |
| def _VerifyNodesList(data): |
| for node in data: |
| for entry in LIST_FIELDS: |
| AssertIn(entry, node) |
| |
| def _VerifyNodesBulk(data): |
| for node_data in data: |
| _VerifyNode(node_data) |
| |
| _DoTests([ |
| ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None), |
| ("/2/nodes", _VerifyNodesList, "GET", None), |
| ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None), |
| ]) |
| |
| |
| def _FilterTags(seq): |
| """Removes unwanted tags from a sequence. |
| |
| """ |
| ignore_re = qa_config.get("ignore-tags-re", None) |
| |
| if ignore_re: |
| return itertools.ifilterfalse(re.compile(ignore_re).match, seq) |
| else: |
| return seq |
| |
| |
| def TestTags(kind, name, tags): |
| """Tests .../tags resources. |
| |
| """ |
| if kind == constants.TAG_CLUSTER: |
| uri = "/2/tags" |
| elif kind == constants.TAG_NODE: |
| uri = "/2/nodes/%s/tags" % name |
| elif kind == constants.TAG_INSTANCE: |
| uri = "/2/instances/%s/tags" % name |
| elif kind == constants.TAG_NODEGROUP: |
| uri = "/2/groups/%s/tags" % name |
| elif kind == constants.TAG_NETWORK: |
| uri = "/2/networks/%s/tags" % name |
| else: |
| raise errors.ProgrammerError("Unknown tag kind") |
| |
| def _VerifyTags(data): |
| AssertEqual(sorted(tags), sorted(_FilterTags(data))) |
| |
| queryargs = "&".join("tag=%s" % i for i in tags) |
| |
| # Add tags |
| (job_id, ) = _DoTests([ |
| ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None), |
| ]) |
| _WaitForRapiJob(job_id) |
| |
| # Retrieve tags |
| _DoTests([ |
| (uri, _VerifyTags, "GET", None), |
| ]) |
| |
| # Remove tags |
| (job_id, ) = _DoTests([ |
| ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None), |
| ]) |
| _WaitForRapiJob(job_id) |
| |
| |
| def _WaitForRapiJob(job_id): |
| """Waits for a job to finish. |
| |
| """ |
| def _VerifyJob(data): |
| AssertEqual(data["id"], job_id) |
| for field in JOB_FIELDS: |
| AssertIn(field, data) |
| |
| _DoTests([ |
| ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None), |
| ]) |
| |
| return rapi.client_utils.PollJob(_rapi_client, job_id, |
| cli.StdioJobPollReportCb()) |
| |
| |
| def TestRapiNodeGroups(): |
| """Test several node group operations using RAPI. |
| |
| """ |
| (group1, group2, group3) = qa_utils.GetNonexistentGroups(3) |
| |
| # Create a group with no attributes |
| body = { |
| "name": group1, |
| } |
| |
| (job_id, ) = _DoTests([ |
| ("/2/groups", _VerifyReturnsJob, "POST", body), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| # Create a group specifying alloc_policy |
| body = { |
| "name": group2, |
| "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE, |
| } |
| |
| (job_id, ) = _DoTests([ |
| ("/2/groups", _VerifyReturnsJob, "POST", body), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| # Modify alloc_policy |
| body = { |
| "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE, |
| } |
| |
| (job_id, ) = _DoTests([ |
| ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| # Rename a group |
| body = { |
| "new_name": group3, |
| } |
| |
| (job_id, ) = _DoTests([ |
| ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| # Delete groups |
| for group in [group1, group3]: |
| (job_id, ) = _DoTests([ |
| ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| |
| def TestRapiInstanceAdd(node, use_client): |
| """Test adding a new instance via RAPI""" |
| if not qa_config.IsTemplateSupported(constants.DT_PLAIN): |
| return |
| instance = qa_config.AcquireInstance() |
| instance.SetDiskTemplate(constants.DT_PLAIN) |
| try: |
| disks = [{"size": utils.ParseUnit(d.get("size")), |
| "name": str(d.get("name"))} |
| for d in qa_config.GetDiskOptions()] |
| nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE) |
| nics = [{ |
| constants.INIC_MAC: nic0_mac, |
| }] |
| |
| beparams = { |
| constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)), |
| constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)), |
| } |
| |
| if use_client: |
| job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE, |
| instance.name, |
| constants.DT_PLAIN, |
| disks, nics, |
| os=qa_config.get("os"), |
| pnode=node.primary, |
| beparams=beparams) |
| else: |
| body = { |
| "__version__": 1, |
| "mode": constants.INSTANCE_CREATE, |
| "name": instance.name, |
| "os_type": qa_config.get("os"), |
| "disk_template": constants.DT_PLAIN, |
| "pnode": node.primary, |
| "beparams": beparams, |
| "disks": disks, |
| "nics": nics, |
| } |
| |
| (job_id, ) = _DoTests([ |
| ("/2/instances", _VerifyReturnsJob, "POST", body), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| return instance |
| except: |
| instance.Release() |
| raise |
| |
| |
| def _GenInstanceAllocationDict(node, instance): |
| """Creates an instance allocation dict to be used with the RAPI""" |
| instance.SetDiskTemplate(constants.DT_PLAIN) |
| |
| disks = [{"size": utils.ParseUnit(d.get("size")), |
| "name": str(d.get("name"))} |
| for d in qa_config.GetDiskOptions()] |
| |
| nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE) |
| nics = [{ |
| constants.INIC_MAC: nic0_mac, |
| }] |
| |
| beparams = { |
| constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)), |
| constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)), |
| } |
| |
| return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE, |
| instance.name, |
| constants.DT_PLAIN, |
| disks, nics, |
| os=qa_config.get("os"), |
| pnode=node.primary, |
| beparams=beparams) |
| |
| |
| def TestRapiInstanceMultiAlloc(node): |
| """Test adding two new instances via the RAPI instance-multi-alloc method""" |
| if not qa_config.IsTemplateSupported(constants.DT_PLAIN): |
| return |
| |
| JOBS_KEY = "jobs" |
| |
| instance_one = qa_config.AcquireInstance() |
| instance_two = qa_config.AcquireInstance() |
| instance_list = [instance_one, instance_two] |
| try: |
| rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node), |
| instance_list) |
| |
| job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts) |
| |
| results, = _WaitForRapiJob(job_id) |
| |
| if JOBS_KEY not in results: |
| raise qa_error.Error("RAPI instance-multi-alloc did not deliver " |
| "information about created jobs") |
| |
| if len(results[JOBS_KEY]) != len(instance_list): |
| raise qa_error.Error("RAPI instance-multi-alloc failed to return the " |
| "desired number of jobs!") |
| |
| for success, job in results[JOBS_KEY]: |
| if success: |
| _WaitForRapiJob(job) |
| else: |
| raise qa_error.Error("Failed to create instance in " |
| "instance-multi-alloc call") |
| except: |
| # Note that although released, it may be that some of the instance creations |
| # have in fact succeeded. Handling this in a better way may be possible, but |
| # is not necessary as the QA has already failed at this point. |
| for instance in instance_list: |
| instance.Release() |
| raise |
| |
| return (instance_one, instance_two) |
| |
| |
| @InstanceCheck(None, INST_DOWN, FIRST_ARG) |
| def TestRapiInstanceRemove(instance, use_client): |
| """Test removing instance via RAPI""" |
| # FIXME: this does not work if LVM is not enabled. Find out if this is a bug |
| # in RAPI or in the test |
| if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG): |
| return |
| |
| if use_client: |
| job_id = _rapi_client.DeleteInstance(instance.name) |
| else: |
| (job_id, ) = _DoTests([ |
| ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None), |
| ]) |
| |
| _WaitForRapiJob(job_id) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestRapiInstanceMigrate(instance): |
| """Test migrating instance via RAPI""" |
| if not IsMigrationSupported(instance): |
| print qa_logging.FormatInfo("Instance doesn't support migration, skipping" |
| " test") |
| return |
| # Move to secondary node |
| _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name)) |
| qa_utils.RunInstanceCheck(instance, True) |
| # And back to previous primary |
| _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name)) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestRapiInstanceFailover(instance): |
| """Test failing over instance via RAPI""" |
| if not IsFailoverSupported(instance): |
| print qa_logging.FormatInfo("Instance doesn't support failover, skipping" |
| " test") |
| return |
| # Move to secondary node |
| _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name)) |
| qa_utils.RunInstanceCheck(instance, True) |
| # And back to previous primary |
| _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name)) |
| |
| |
| @InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG) |
| def TestRapiInstanceShutdown(instance): |
| """Test stopping an instance via RAPI""" |
| _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name)) |
| |
| |
| @InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG) |
| def TestRapiInstanceStartup(instance): |
| """Test starting an instance via RAPI""" |
| _WaitForRapiJob(_rapi_client.StartupInstance(instance.name)) |
| |
| |
| @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG) |
| def TestRapiInstanceRenameAndBack(rename_source, rename_target): |
| """Test renaming instance via RAPI |
| |
| This must leave the instance with the original name (in the |
| non-failure case). |
| |
| """ |
| _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target)) |
| qa_utils.RunInstanceCheck(rename_source, False) |
| qa_utils.RunInstanceCheck(rename_target, False) |
| _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source)) |
| qa_utils.RunInstanceCheck(rename_target, False) |
| |
| |
| @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG) |
| def TestRapiInstanceReinstall(instance): |
| """Test reinstalling an instance via RAPI""" |
| if instance.disk_template == constants.DT_DISKLESS: |
| print qa_logging.FormatInfo("Test not supported for diskless instances") |
| return |
| |
| _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name)) |
| # By default, the instance is started again |
| qa_utils.RunInstanceCheck(instance, True) |
| |
| # Reinstall again without starting |
| _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name, |
| no_startup=True)) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestRapiInstanceReplaceDisks(instance): |
| """Test replacing instance disks via RAPI""" |
| if not IsDiskReplacingSupported(instance): |
| print qa_logging.FormatInfo("Instance doesn't support disk replacing," |
| " skipping test") |
| return |
| fn = _rapi_client.ReplaceInstanceDisks |
| _WaitForRapiJob(fn(instance.name, |
| mode=constants.REPLACE_DISK_AUTO, disks=[])) |
| _WaitForRapiJob(fn(instance.name, |
| mode=constants.REPLACE_DISK_SEC, disks="0")) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestRapiInstanceModify(instance): |
| """Test modifying instance via RAPI""" |
| default_hv = qa_config.GetDefaultHypervisor() |
| |
| def _ModifyInstance(**kwargs): |
| _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs)) |
| |
| _ModifyInstance(beparams={ |
| constants.BE_VCPUS: 3, |
| }) |
| |
| _ModifyInstance(beparams={ |
| constants.BE_VCPUS: constants.VALUE_DEFAULT, |
| }) |
| |
| if default_hv == constants.HT_XEN_PVM: |
| _ModifyInstance(hvparams={ |
| constants.HV_KERNEL_ARGS: "single", |
| }) |
| _ModifyInstance(hvparams={ |
| constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT, |
| }) |
| elif default_hv == constants.HT_XEN_HVM: |
| _ModifyInstance(hvparams={ |
| constants.HV_BOOT_ORDER: "acn", |
| }) |
| _ModifyInstance(hvparams={ |
| constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT, |
| }) |
| |
| |
| @InstanceCheck(INST_UP, INST_UP, FIRST_ARG) |
| def TestRapiInstanceConsole(instance): |
| """Test getting instance console information via RAPI""" |
| result = _rapi_client.GetInstanceConsole(instance.name) |
| console = objects.InstanceConsole.FromDict(result) |
| AssertEqual(console.Validate(), True) |
| AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name)) |
| |
| |
| @InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG) |
| def TestRapiStoppedInstanceConsole(instance): |
| """Test getting stopped instance's console information via RAPI""" |
| try: |
| _rapi_client.GetInstanceConsole(instance.name) |
| except rapi.client.GanetiApiError, err: |
| AssertEqual(err.code, 503) |
| else: |
| raise qa_error.Error("Getting console for stopped instance didn't" |
| " return HTTP 503") |
| |
| |
| def GetOperatingSystems(): |
| """Retrieves a list of all available operating systems. |
| |
| """ |
| return _rapi_client.GetOperatingSystems() |
| |
| |
| def TestInterClusterInstanceMove(src_instance, dest_instance, |
| inodes, tnode): |
| """Test tools/move-instance""" |
| master = qa_config.GetMasterNode() |
| |
| rapi_pw_file = tempfile.NamedTemporaryFile() |
| rapi_pw_file.write(_rapi_password) |
| rapi_pw_file.flush() |
| |
| dest_instance.SetDiskTemplate(src_instance.disk_template) |
| |
| # TODO: Run some instance tests before moving back |
| |
| if len(inodes) > 1: |
| # No disk template currently requires more than 1 secondary node. If this |
| # changes, either this test must be skipped or the script must be updated. |
| assert len(inodes) == 2 |
| snode = inodes[1] |
| else: |
| # instance is not redundant, but we still need to pass a node |
| # (which will be ignored) |
| snode = tnode |
| pnode = inodes[0] |
| # note: pnode:snode are the *current* nodes, so we move it first to |
| # tnode:pnode, then back to pnode:snode |
| for si, di, pn, sn in [(src_instance.name, dest_instance.name, |
| tnode.primary, pnode.primary), |
| (dest_instance.name, src_instance.name, |
| pnode.primary, snode.primary)]: |
| cmd = [ |
| "../tools/move-instance", |
| "--verbose", |
| "--src-ca-file=%s" % _rapi_ca.name, |
| "--src-username=%s" % _rapi_username, |
| "--src-password-file=%s" % rapi_pw_file.name, |
| "--dest-instance-name=%s" % di, |
| "--dest-primary-node=%s" % pn, |
| "--dest-secondary-node=%s" % sn, |
| "--net=0:mac=%s" % constants.VALUE_GENERATE, |
| master.primary, |
| master.primary, |
| si, |
| ] |
| |
| qa_utils.RunInstanceCheck(di, False) |
| AssertEqual(StartLocalCommand(cmd).wait(), 0) |
| qa_utils.RunInstanceCheck(si, False) |
| qa_utils.RunInstanceCheck(di, True) |
| |
| |
| _DRBD_SECRET_RE = re.compile('shared-secret.*"([0-9A-Fa-f]+)"') |
| |
| |
| def _RetrieveSecret(instance, pnode): |
| """Retrieves the DRBD secret given an instance object and the primary node. |
| |
| @type instance: L{qa_config._QaInstance} |
| @type pnode: L{qa_config._QaNode} |
| |
| @rtype: string |
| |
| """ |
| instance_info = GetInstanceInfo(instance.name) |
| |
| # We are interested in only the first disk on the primary |
| drbd_minor = instance_info["drbd-minors"][pnode.primary][0] |
| |
| # This form should work for all DRBD versions |
| drbd_command = ("drbdsetup show %d; drbdsetup %d show || true" % |
| (drbd_minor, drbd_minor)) |
| instance_drbd_info = \ |
| qa_utils.GetCommandOutput(pnode.primary, drbd_command) |
| |
| match_obj = _DRBD_SECRET_RE.search(instance_drbd_info) |
| if match_obj is None: |
| raise qa_error.Error("Could not retrieve DRBD secret for instance %s from" |
| " node %s." % (instance.name, pnode.primary)) |
| |
| return match_obj.groups(0)[0] |
| |
| |
| def TestInstanceDataCensorship(instance, inodes): |
| """Test protection of sensitive instance data.""" |
| |
| if instance.disk_template != constants.DT_DRBD8: |
| print qa_utils.FormatInfo("Only the DRBD secret is a sensitive parameter" |
| " right now, skipping for non-DRBD instance.") |
| return |
| |
| drbd_secret = _RetrieveSecret(instance, inodes[0]) |
| |
| job_id = _rapi_client.GetInstanceInfo(instance.name) |
| if not _rapi_client.WaitForJobCompletion(job_id): |
| raise qa_error.Error("Could not fetch instance info for instance %s" % |
| instance.name) |
| info_dict = _rapi_client.GetJobStatus(job_id) |
| |
| if drbd_secret in str(info_dict): |
| print qa_utils.FormatInfo("DRBD secret: %s" % drbd_secret) |
| print qa_utils.FormatInfo("Retrieved data\n%s" % str(info_dict)) |
| raise qa_error.Error("Found DRBD secret in contents of RAPI instance info" |
| " call; see above.") |