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),
182 def TestInstance(instance):
183 """Testing getting instance(s) info via remote API.
186 def _VerifyInstance(data):
187 for entry in INSTANCE_FIELDS:
188 AssertIn(entry, data)
190 def _VerifyInstancesList(data):
191 for instance in data:
192 for entry in LIST_FIELDS:
193 AssertIn(entry, instance)
195 def _VerifyInstancesBulk(data):
196 for instance_data in data:
197 _VerifyInstance(instance_data)
200 ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
201 ("/2/instances", _VerifyInstancesList, 'GET', None),
202 ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
203 ("/2/instances/%s/activate-disks" % instance["name"],
204 _VerifyReturnsJob, 'PUT', None),
205 ("/2/instances/%s/deactivate-disks" % instance["name"],
206 _VerifyReturnsJob, 'PUT', None),
209 # Test OpPrepareExport
210 (job_id, ) = _DoTests([
211 ("/2/instances/%s/prepare-export?mode=%s" %
212 (instance["name"], constants.EXPORT_MODE_REMOTE),
213 _VerifyReturnsJob, "PUT", None),
216 result = _WaitForRapiJob(job_id)[0]
217 AssertEqual(len(result["handshake"]), 3)
218 AssertEqual(result["handshake"][0], constants.RIE_VERSION)
219 AssertEqual(len(result["x509_key_name"]), 3)
220 AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
224 """Testing getting node(s) info via remote API.
227 def _VerifyNode(data):
228 for entry in NODE_FIELDS:
229 AssertIn(entry, data)
231 def _VerifyNodesList(data):
233 for entry in LIST_FIELDS:
234 AssertIn(entry, node)
236 def _VerifyNodesBulk(data):
237 for node_data in data:
238 _VerifyNode(node_data)
241 ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
242 ("/2/nodes", _VerifyNodesList, 'GET', None),
243 ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
247 def TestTags(kind, name, tags):
248 """Tests .../tags resources.
251 if kind == constants.TAG_CLUSTER:
253 elif kind == constants.TAG_NODE:
254 uri = "/2/nodes/%s/tags" % name
255 elif kind == constants.TAG_INSTANCE:
256 uri = "/2/instances/%s/tags" % name
258 raise errors.ProgrammerError("Unknown tag kind")
260 def _VerifyTags(data):
261 AssertEqual(sorted(tags), sorted(data))
263 query = "&".join("tag=%s" % i for i in tags)
266 (job_id, ) = _DoTests([
267 ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
269 _WaitForRapiJob(job_id)
273 (uri, _VerifyTags, 'GET', None),
277 (job_id, ) = _DoTests([
278 ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
280 _WaitForRapiJob(job_id)
283 def _WaitForRapiJob(job_id):
284 """Waits for a job to finish.
287 master = qa_config.GetMasterNode()
289 def _VerifyJob(data):
290 AssertEqual(data["id"], job_id)
291 for field in JOB_FIELDS:
292 AssertIn(field, data)
295 ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
298 return rapi.client_utils.PollJob(_rapi_client, job_id,
299 cli.StdioJobPollReportCb())
302 def TestRapiInstanceAdd(node, use_client):
303 """Test adding a new instance via RAPI"""
304 instance = qa_config.AcquireInstance()
306 memory = utils.ParseUnit(qa_config.get("mem"))
307 disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
310 disks = [{"size": size} for size in disk_sizes]
314 constants.BE_MEMORY: memory,
317 job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
321 os=qa_config.get("os"),
322 pnode=node["primary"],
326 "name": instance["name"],
327 "os": qa_config.get("os"),
328 "disk_template": constants.DT_PLAIN,
329 "pnode": node["primary"],
334 (job_id, ) = _DoTests([
335 ("/2/instances", _VerifyReturnsJob, "POST", body),
338 _WaitForRapiJob(job_id)
342 qa_config.ReleaseInstance(instance)
346 def TestRapiInstanceRemove(instance, use_client):
347 """Test removing instance via RAPI"""
349 job_id = _rapi_client.DeleteInstance(instance["name"])
351 (job_id, ) = _DoTests([
352 ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
355 _WaitForRapiJob(job_id)
357 qa_config.ReleaseInstance(instance)
360 def TestRapiInstanceMigrate(instance):
361 """Test migrating instance via RAPI"""
362 # Move to secondary node
363 _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
364 # And back to previous primary
365 _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
368 def TestRapiInstanceRename(instance, rename_target):
369 """Test renaming instance via RAPI"""
370 rename_source = instance["name"]
372 for name1, name2 in [(rename_source, rename_target),
373 (rename_target, rename_source)]:
374 _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
377 def TestRapiInstanceModify(instance):
378 """Test modifying instance via RAPI"""
379 def _ModifyInstance(**kwargs):
380 _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
382 _ModifyInstance(hvparams={
383 constants.HV_KERNEL_ARGS: "single",
386 _ModifyInstance(beparams={
387 constants.BE_VCPUS: 3,
390 _ModifyInstance(beparams={
391 constants.BE_VCPUS: constants.VALUE_DEFAULT,
394 _ModifyInstance(hvparams={
395 constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
399 def TestInterClusterInstanceMove(src_instance, dest_instance,
400 pnode, snode, tnode):
401 """Test tools/move-instance"""
402 master = qa_config.GetMasterNode()
404 rapi_pw_file = tempfile.NamedTemporaryFile()
405 rapi_pw_file.write(_rapi_password)
408 # TODO: Run some instance tests before moving back
411 # instance is not redundant, but we still need to pass a node
412 # (which will be ignored)
416 # note: pnode:snode are the *current* nodes, so we move it first to
417 # tnode:pnode, then back to pnode:snode
418 for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
419 tnode["primary"], pnode["primary"]),
420 (dest_instance["name"], src_instance["name"],
421 pnode["primary"], fsec["primary"])]:
423 "../tools/move-instance",
425 "--src-ca-file=%s" % _rapi_ca.name,
426 "--src-username=%s" % _rapi_username,
427 "--src-password-file=%s" % rapi_pw_file.name,
428 "--dest-instance-name=%s" % di,
429 "--dest-primary-node=%s" % pn,
430 "--dest-secondary-node=%s" % sn,
431 "--net=0:mac=%s" % constants.VALUE_GENERATE,
437 AssertEqual(StartLocalCommand(cmd).wait(), 0)