blob: 08bb659c1ac26d2346e16400518c007cc251d41c [file] [log] [blame]
#
#
# 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.")