Newer
Older
# Copyright (C) 2007, 2008, 2009, 2010, 2011 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.
"""
from ganeti import utils
from ganeti import constants
from ganeti import errors
from ganeti import cli
from ganeti import rapi
import ganeti.rapi.client # pylint: disable-msg=W0611
import qa_config
import qa_utils
import qa_error
from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
_rapi_username = None
_rapi_password = None
def Setup(username, password):
"""Configures the RAPI client.
# pylint: disable-msg=W0603
# due to global usage
global _rapi_username
global _rapi_password
_rapi_username = username
_rapi_password = password
# Load RAPI certificate from master node
cmd = ["cat", constants.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="")
_rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
username=username,
password=password,
print "RAPI protocol version: %s" % _rapi_client.GetVersion()
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
"admin_state",
"disk_template", "disk.sizes",
"nic.ips", "nic.macs", "nic.modes", "nic.links",
"oper_state", "oper_ram", "oper_vcpus", "status", "tags")
NODE_FIELDS = ("name", "dtotal", "dfree",
"mtotal", "mnode", "mfree",
"pinst_cnt", "sinst_cnt", "tags")
GROUP_FIELDS = frozenset([
"name", "uuid",
JOB_FIELDS = frozenset([
"id", "ops", "status", "summary",
"opstatus", "opresult", "oplog",
"received_ts", "start_ts", "end_ts",
])
def Enabled():
"""Return whether remote API tests should be run.
"""
# pylint: disable-msg=W0212
# due to _SendRequest usage
for uri, verify, method, body in uris:
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)
return results
def _VerifyReturnsJob(data):
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)
("/", 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 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:
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),
(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:
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 TestTags(kind, name, tags):
"""Tests .../tags resources.
"""
if kind == constants.TAG_CLUSTER:
uri = "/2/instances/%s/tags" % name
else:
raise errors.ProgrammerError("Unknown tag kind")
def _VerifyTags(data):
AssertEqual(sorted(tags), sorted(data))
query = "&".join("tag=%s" % i for i in tags)
# Add tags
(job_id, ) = _DoTests([
("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
])
_WaitForRapiJob(job_id)
# Retrieve tags
# Remove tags
(job_id, ) = _DoTests([
("%s?%s" % (uri, query), _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())
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
def TestRapiNodeGroups():
"""Test several node group operations using RAPI.
"""
groups = qa_config.get("groups", {})
group1, group2, group3 = groups.get("inexistent-groups",
["group1", "group2", "group3"])[: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"""
instance = qa_config.AcquireInstance()
try:
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
memory = utils.ParseUnit(qa_config.get("mem"))
disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
if use_client:
disks = [{"size": size} for size in disk_sizes]
nics = [{}]
beparams = {
constants.BE_MEMORY: memory,
}
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 = {
"name": instance["name"],
"os": qa_config.get("os"),
"disk_template": constants.DT_PLAIN,
"pnode": node["primary"],
"memory": memory,
"disks": disk_sizes,
}
(job_id, ) = _DoTests([
("/2/instances", _VerifyReturnsJob, "POST", body),
])
_WaitForRapiJob(job_id)
return instance
except:
qa_config.ReleaseInstance(instance)
raise
def TestRapiInstanceRemove(instance, use_client):
"""Test removing instance via RAPI"""
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)
qa_config.ReleaseInstance(instance)
def TestRapiInstanceMigrate(instance):
"""Test migrating instance via RAPI"""
# Move to secondary node
_WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
# And back to previous primary
_WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
def TestRapiInstanceRename(rename_source, rename_target):
"""Test renaming instance via RAPI"""
_WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
def TestRapiInstanceReinstall(instance):
"""Test reinstalling an instance via RAPI"""
_WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
def TestRapiInstanceModify(instance):
"""Test modifying instance via RAPI"""
def _ModifyInstance(**kwargs):
_WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
_ModifyInstance(hvparams={
constants.HV_KERNEL_ARGS: "single",
})
_ModifyInstance(beparams={
constants.BE_VCPUS: 3,
})
_ModifyInstance(beparams={
constants.BE_VCPUS: constants.VALUE_DEFAULT,
})
_ModifyInstance(hvparams={
constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
})
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"]))
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 TestInterClusterInstanceMove(src_instance, dest_instance,
pnode, snode, tnode):
"""Test tools/move-instance"""
master = qa_config.GetMasterNode()
rapi_pw_file = tempfile.NamedTemporaryFile()
rapi_pw_file.write(_rapi_password)
rapi_pw_file.flush()
# TODO: Run some instance tests before moving back
if snode is None:
# instance is not redundant, but we still need to pass a node
# (which will be ignored)
fsec = tnode
else:
fsec = snode
# 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"]),
pnode["primary"], fsec["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"],
]
AssertEqual(StartLocalCommand(cmd).wait(), 0)