3 # Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """Remote API QA tests.
27 from ganeti import utils
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import serializer
31 from ganeti import cli
32 from ganeti import rapi
34 import ganeti.rapi.client
35 import ganeti.rapi.client_utils
41 from qa_utils import (AssertEqual, AssertNotEqual, AssertIn, AssertMatch,
51 def Setup(username, password):
52 """Configures the RAPI client.
60 _rapi_username = username
61 _rapi_password = password
63 master = qa_config.GetMasterNode()
65 # Load RAPI certificate from master node
66 cmd = ["cat", constants.RAPI_CERT_FILE]
68 # Write to temporary file
69 _rapi_ca = tempfile.NamedTemporaryFile()
70 _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
71 utils.ShellQuoteArgs(cmd)))
74 port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
75 cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
78 _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
81 curl_config_fn=cfg_curl)
83 print "RAPI protocol version: %s" % _rapi_client.GetVersion()
86 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
88 "disk_template", "disk.sizes",
89 "nic.ips", "nic.macs", "nic.modes", "nic.links",
90 "beparams", "hvparams",
91 "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
93 NODE_FIELDS = ("name", "dtotal", "dfree",
94 "mtotal", "mnode", "mfree",
95 "pinst_cnt", "sinst_cnt", "tags")
97 JOB_FIELDS = frozenset([
98 "id", "ops", "status", "summary",
99 "opstatus", "opresult", "oplog",
100 "received_ts", "start_ts", "end_ts",
103 LIST_FIELDS = ("id", "uri")
107 """Return whether remote API tests should be run.
110 return qa_config.TestEnabled('rapi')
116 for uri, verify, method, body in uris:
117 assert uri.startswith("/")
119 print "%s %s" % (method, uri)
120 data = _rapi_client._SendRequest(method, uri, None, body)
122 if verify is not None:
126 AssertEqual(data, verify)
133 def _VerifyReturnsJob(data):
134 AssertMatch(data, r'^\d+$')
138 """Testing remote API version.
142 ("/version", constants.RAPI_VERSION, 'GET', None),
146 def TestEmptyCluster():
147 """Testing remote API on an empty cluster.
150 master = qa_config.GetMasterNode()
151 master_full = qa_utils.ResolveNodeName(master)
153 def _VerifyInfo(data):
154 AssertIn("name", data)
155 AssertIn("master", data)
156 AssertEqual(data["master"], master_full)
158 def _VerifyNodes(data):
161 "uri": "/2/nodes/%s" % master_full,
163 AssertIn(master_entry, data)
165 def _VerifyNodesBulk(data):
167 for entry in NODE_FIELDS:
168 AssertIn(entry, node)
171 ("/", None, 'GET', None),
172 ("/2/info", _VerifyInfo, 'GET', None),
173 ("/2/tags", None, 'GET', None),
174 ("/2/nodes", _VerifyNodes, 'GET', None),
175 ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
176 ("/2/instances", [], 'GET', None),
177 ("/2/instances?bulk=1", [], 'GET', None),
178 ("/2/os", None, 'GET', None),
181 # Test HTTP Not Found
182 for method in ["GET", "PUT", "POST", "DELETE"]:
184 _DoTests([("/99/resource/not/here/99", None, method, None)])
185 except rapi.client.GanetiApiError, err:
186 AssertEqual(err.code, 404)
188 raise qa_error.Error("Non-existent resource didn't return HTTP 404")
190 # Test HTTP Not Implemented
191 for method in ["PUT", "POST", "DELETE"]:
193 _DoTests([("/version", None, method, None)])
194 except rapi.client.GanetiApiError, err:
195 AssertEqual(err.code, 501)
197 raise qa_error.Error("Non-implemented method didn't fail")
200 def TestInstance(instance):
201 """Testing getting instance(s) info via remote API.
204 def _VerifyInstance(data):
205 for entry in INSTANCE_FIELDS:
206 AssertIn(entry, data)
208 def _VerifyInstancesList(data):
209 for instance in data:
210 for entry in LIST_FIELDS:
211 AssertIn(entry, instance)
213 def _VerifyInstancesBulk(data):
214 for instance_data in data:
215 _VerifyInstance(instance_data)
218 ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
219 ("/2/instances", _VerifyInstancesList, 'GET', None),
220 ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
221 ("/2/instances/%s/activate-disks" % instance["name"],
222 _VerifyReturnsJob, 'PUT', None),
223 ("/2/instances/%s/deactivate-disks" % instance["name"],
224 _VerifyReturnsJob, 'PUT', None),
227 # Test OpPrepareExport
228 (job_id, ) = _DoTests([
229 ("/2/instances/%s/prepare-export?mode=%s" %
230 (instance["name"], constants.EXPORT_MODE_REMOTE),
231 _VerifyReturnsJob, "PUT", None),
234 result = _WaitForRapiJob(job_id)[0]
235 AssertEqual(len(result["handshake"]), 3)
236 AssertEqual(result["handshake"][0], constants.RIE_VERSION)
237 AssertEqual(len(result["x509_key_name"]), 3)
238 AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
242 """Testing getting node(s) info via remote API.
245 def _VerifyNode(data):
246 for entry in NODE_FIELDS:
247 AssertIn(entry, data)
249 def _VerifyNodesList(data):
251 for entry in LIST_FIELDS:
252 AssertIn(entry, node)
254 def _VerifyNodesBulk(data):
255 for node_data in data:
256 _VerifyNode(node_data)
259 ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
260 ("/2/nodes", _VerifyNodesList, 'GET', None),
261 ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
265 def TestTags(kind, name, tags):
266 """Tests .../tags resources.
269 if kind == constants.TAG_CLUSTER:
271 elif kind == constants.TAG_NODE:
272 uri = "/2/nodes/%s/tags" % name
273 elif kind == constants.TAG_INSTANCE:
274 uri = "/2/instances/%s/tags" % name
276 raise errors.ProgrammerError("Unknown tag kind")
278 def _VerifyTags(data):
279 AssertEqual(sorted(tags), sorted(data))
281 query = "&".join("tag=%s" % i for i in tags)
284 (job_id, ) = _DoTests([
285 ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
287 _WaitForRapiJob(job_id)
291 (uri, _VerifyTags, 'GET', None),
295 (job_id, ) = _DoTests([
296 ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
298 _WaitForRapiJob(job_id)
301 def _WaitForRapiJob(job_id):
302 """Waits for a job to finish.
305 master = qa_config.GetMasterNode()
307 def _VerifyJob(data):
308 AssertEqual(data["id"], job_id)
309 for field in JOB_FIELDS:
310 AssertIn(field, data)
313 ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
316 return rapi.client_utils.PollJob(_rapi_client, job_id,
317 cli.StdioJobPollReportCb())
320 def TestRapiInstanceAdd(node, use_client):
321 """Test adding a new instance via RAPI"""
322 instance = qa_config.AcquireInstance()
324 memory = utils.ParseUnit(qa_config.get("mem"))
325 disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
328 disks = [{"size": size} for size in disk_sizes]
332 constants.BE_MEMORY: memory,
335 job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
339 os=qa_config.get("os"),
340 pnode=node["primary"],
344 "name": instance["name"],
345 "os": qa_config.get("os"),
346 "disk_template": constants.DT_PLAIN,
347 "pnode": node["primary"],
352 (job_id, ) = _DoTests([
353 ("/2/instances", _VerifyReturnsJob, "POST", body),
356 _WaitForRapiJob(job_id)
360 qa_config.ReleaseInstance(instance)
364 def TestRapiInstanceRemove(instance, use_client):
365 """Test removing instance via RAPI"""
367 job_id = _rapi_client.DeleteInstance(instance["name"])
369 (job_id, ) = _DoTests([
370 ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
373 _WaitForRapiJob(job_id)
375 qa_config.ReleaseInstance(instance)
378 def TestRapiInstanceMigrate(instance):
379 """Test migrating instance via RAPI"""
380 # Move to secondary node
381 _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
382 # And back to previous primary
383 _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
386 def TestRapiInstanceRename(instance, rename_target):
387 """Test renaming instance via RAPI"""
388 rename_source = instance["name"]
390 for name1, name2 in [(rename_source, rename_target),
391 (rename_target, rename_source)]:
392 _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
395 def TestRapiInstanceModify(instance):
396 """Test modifying instance via RAPI"""
397 def _ModifyInstance(**kwargs):
398 _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
400 _ModifyInstance(hvparams={
401 constants.HV_KERNEL_ARGS: "single",
404 _ModifyInstance(beparams={
405 constants.BE_VCPUS: 3,
408 _ModifyInstance(beparams={
409 constants.BE_VCPUS: constants.VALUE_DEFAULT,
412 _ModifyInstance(hvparams={
413 constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
417 def TestInterClusterInstanceMove(src_instance, dest_instance,
418 pnode, snode, tnode):
419 """Test tools/move-instance"""
420 master = qa_config.GetMasterNode()
422 rapi_pw_file = tempfile.NamedTemporaryFile()
423 rapi_pw_file.write(_rapi_password)
426 # TODO: Run some instance tests before moving back
429 # instance is not redundant, but we still need to pass a node
430 # (which will be ignored)
434 # note: pnode:snode are the *current* nodes, so we move it first to
435 # tnode:pnode, then back to pnode:snode
436 for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
437 tnode["primary"], pnode["primary"]),
438 (dest_instance["name"], src_instance["name"],
439 pnode["primary"], fsec["primary"])]:
441 "../tools/move-instance",
443 "--src-ca-file=%s" % _rapi_ca.name,
444 "--src-username=%s" % _rapi_username,
445 "--src-password-file=%s" % rapi_pw_file.name,
446 "--dest-instance-name=%s" % di,
447 "--dest-primary-node=%s" % pn,
448 "--dest-secondary-node=%s" % sn,
449 "--net=0:mac=%s" % constants.VALUE_GENERATE,
455 AssertEqual(StartLocalCommand(cmd).wait(), 0)