Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 4d2bd00a

History | View | Annotate | Download (12.1 kB)

1
#
2

    
3
# Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
4
#
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.
9
#
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.
14
#
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
18
# 02110-1301, USA.
19

    
20

    
21
"""Remote API QA tests.
22

23
"""
24

    
25
import tempfile
26

    
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
33

    
34
import ganeti.rapi.client
35
import ganeti.rapi.client_utils
36

    
37
import qa_config
38
import qa_utils
39
import qa_error
40

    
41
from qa_utils import (AssertEqual, AssertNotEqual, AssertIn, AssertMatch,
42
                      StartLocalCommand)
43

    
44

    
45
_rapi_ca = None
46
_rapi_client = None
47
_rapi_username = None
48
_rapi_password = None
49

    
50

    
51
def Setup(username, password):
52
  """Configures the RAPI client.
53

54
  """
55
  global _rapi_ca
56
  global _rapi_client
57
  global _rapi_username
58
  global _rapi_password
59

    
60
  _rapi_username = username
61
  _rapi_password = password
62

    
63
  master = qa_config.GetMasterNode()
64

    
65
  # Load RAPI certificate from master node
66
  cmd = ["cat", constants.RAPI_CERT_FILE]
67

    
68
  # Write to temporary file
69
  _rapi_ca = tempfile.NamedTemporaryFile()
70
  _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
71
                                           utils.ShellQuoteArgs(cmd)))
72
  _rapi_ca.flush()
73

    
74
  port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
75
  cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
76
                                           proxy="")
77

    
78
  _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
79
                                              username=username,
80
                                              password=password,
81
                                              curl_config_fn=cfg_curl)
82

    
83
  print "RAPI protocol version: %s" % _rapi_client.GetVersion()
84

    
85

    
86
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
87
                   "admin_state",
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")
92

    
93
NODE_FIELDS = ("name", "dtotal", "dfree",
94
               "mtotal", "mnode", "mfree",
95
               "pinst_cnt", "sinst_cnt", "tags")
96

    
97
JOB_FIELDS = frozenset([
98
  "id", "ops", "status", "summary",
99
  "opstatus", "opresult", "oplog",
100
  "received_ts", "start_ts", "end_ts",
101
  ])
102

    
103
LIST_FIELDS = ("id", "uri")
104

    
105

    
106
def Enabled():
107
  """Return whether remote API tests should be run.
108

109
  """
110
  return qa_config.TestEnabled('rapi')
111

    
112

    
113
def _DoTests(uris):
114
  results = []
115

    
116
  for uri, verify, method, body in uris:
117
    assert uri.startswith("/")
118

    
119
    print "%s %s" % (method, uri)
120
    data = _rapi_client._SendRequest(method, uri, None, body)
121

    
122
    if verify is not None:
123
      if callable(verify):
124
        verify(data)
125
      else:
126
        AssertEqual(data, verify)
127

    
128
    results.append(data)
129

    
130
  return results
131

    
132

    
133
def _VerifyReturnsJob(data):
134
  AssertMatch(data, r'^\d+$')
135

    
136

    
137
def TestVersion():
138
  """Testing remote API version.
139

140
  """
141
  _DoTests([
142
    ("/version", constants.RAPI_VERSION, 'GET', None),
143
    ])
144

    
145

    
146
def TestEmptyCluster():
147
  """Testing remote API on an empty cluster.
148

149
  """
150
  master = qa_config.GetMasterNode()
151
  master_full = qa_utils.ResolveNodeName(master)
152

    
153
  def _VerifyInfo(data):
154
    AssertIn("name", data)
155
    AssertIn("master", data)
156
    AssertEqual(data["master"], master_full)
157

    
158
  def _VerifyNodes(data):
159
    master_entry = {
160
      "id": master_full,
161
      "uri": "/2/nodes/%s" % master_full,
162
      }
163
    AssertIn(master_entry, data)
164

    
165
  def _VerifyNodesBulk(data):
166
    for node in data:
167
      for entry in NODE_FIELDS:
168
        AssertIn(entry, node)
169

    
170
  _DoTests([
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),
179
    ])
180

    
181
  # Test HTTP Not Found
182
  for method in ["GET", "PUT", "POST", "DELETE"]:
183
    try:
184
      _DoTests([("/99/resource/not/here/99", None, method, None)])
185
    except rapi.client.GanetiApiError, err:
186
      AssertEqual(err.code, 404)
187
    else:
188
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
189

    
190
  # Test HTTP Not Implemented
191
  for method in ["PUT", "POST", "DELETE"]:
192
    try:
193
      _DoTests([("/version", None, method, None)])
194
    except rapi.client.GanetiApiError, err:
195
      AssertEqual(err.code, 501)
196
    else:
197
      raise qa_error.Error("Non-implemented method didn't fail")
198

    
199

    
200
def TestInstance(instance):
201
  """Testing getting instance(s) info via remote API.
202

203
  """
204
  def _VerifyInstance(data):
205
    for entry in INSTANCE_FIELDS:
206
      AssertIn(entry, data)
207

    
208
  def _VerifyInstancesList(data):
209
    for instance in data:
210
      for entry in LIST_FIELDS:
211
        AssertIn(entry, instance)
212

    
213
  def _VerifyInstancesBulk(data):
214
    for instance_data in data:
215
      _VerifyInstance(instance_data)
216

    
217
  _DoTests([
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),
225
    ])
226

    
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),
232
    ])
233

    
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"])
239

    
240

    
241
def TestNode(node):
242
  """Testing getting node(s) info via remote API.
243

244
  """
245
  def _VerifyNode(data):
246
    for entry in NODE_FIELDS:
247
      AssertIn(entry, data)
248

    
249
  def _VerifyNodesList(data):
250
    for node in data:
251
      for entry in LIST_FIELDS:
252
        AssertIn(entry, node)
253

    
254
  def _VerifyNodesBulk(data):
255
    for node_data in data:
256
      _VerifyNode(node_data)
257

    
258
  _DoTests([
259
    ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
260
    ("/2/nodes", _VerifyNodesList, 'GET', None),
261
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
262
    ])
263

    
264

    
265
def TestTags(kind, name, tags):
266
  """Tests .../tags resources.
267

268
  """
269
  if kind == constants.TAG_CLUSTER:
270
    uri = "/2/tags"
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
275
  else:
276
    raise errors.ProgrammerError("Unknown tag kind")
277

    
278
  def _VerifyTags(data):
279
    AssertEqual(sorted(tags), sorted(data))
280

    
281
  query = "&".join("tag=%s" % i for i in tags)
282

    
283
  # Add tags
284
  (job_id, ) = _DoTests([
285
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
286
    ])
287
  _WaitForRapiJob(job_id)
288

    
289
  # Retrieve tags
290
  _DoTests([
291
    (uri, _VerifyTags, 'GET', None),
292
    ])
293

    
294
  # Remove tags
295
  (job_id, ) = _DoTests([
296
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
297
    ])
298
  _WaitForRapiJob(job_id)
299

    
300

    
301
def _WaitForRapiJob(job_id):
302
  """Waits for a job to finish.
303

304
  """
305
  master = qa_config.GetMasterNode()
306

    
307
  def _VerifyJob(data):
308
    AssertEqual(data["id"], job_id)
309
    for field in JOB_FIELDS:
310
      AssertIn(field, data)
311

    
312
  _DoTests([
313
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
314
    ])
315

    
316
  return rapi.client_utils.PollJob(_rapi_client, job_id,
317
                                   cli.StdioJobPollReportCb())
318

    
319

    
320
def TestRapiInstanceAdd(node, use_client):
321
  """Test adding a new instance via RAPI"""
322
  instance = qa_config.AcquireInstance()
323
  try:
324
    memory = utils.ParseUnit(qa_config.get("mem"))
325
    disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
326

    
327
    if use_client:
328
      disks = [{"size": size} for size in disk_sizes]
329
      nics = [{}]
330

    
331
      beparams = {
332
        constants.BE_MEMORY: memory,
333
        }
334

    
335
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
336
                                           instance["name"],
337
                                           constants.DT_PLAIN,
338
                                           disks, nics,
339
                                           os=qa_config.get("os"),
340
                                           pnode=node["primary"],
341
                                           beparams=beparams)
342
    else:
343
      body = {
344
        "name": instance["name"],
345
        "os": qa_config.get("os"),
346
        "disk_template": constants.DT_PLAIN,
347
        "pnode": node["primary"],
348
        "memory": memory,
349
        "disks": disk_sizes,
350
        }
351

    
352
      (job_id, ) = _DoTests([
353
        ("/2/instances", _VerifyReturnsJob, "POST", body),
354
        ])
355

    
356
    _WaitForRapiJob(job_id)
357

    
358
    return instance
359
  except:
360
    qa_config.ReleaseInstance(instance)
361
    raise
362

    
363

    
364
def TestRapiInstanceRemove(instance, use_client):
365
  """Test removing instance via RAPI"""
366
  if use_client:
367
    job_id = _rapi_client.DeleteInstance(instance["name"])
368
  else:
369
    (job_id, ) = _DoTests([
370
      ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
371
      ])
372

    
373
  _WaitForRapiJob(job_id)
374

    
375
  qa_config.ReleaseInstance(instance)
376

    
377

    
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"]))
384

    
385

    
386
def TestRapiInstanceRename(instance, rename_target):
387
  """Test renaming instance via RAPI"""
388
  rename_source = instance["name"]
389

    
390
  for name1, name2 in [(rename_source, rename_target),
391
                       (rename_target, rename_source)]:
392
    _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
393

    
394

    
395
def TestRapiInstanceModify(instance):
396
  """Test modifying instance via RAPI"""
397
  def _ModifyInstance(**kwargs):
398
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
399

    
400
  _ModifyInstance(hvparams={
401
    constants.HV_KERNEL_ARGS: "single",
402
    })
403

    
404
  _ModifyInstance(beparams={
405
    constants.BE_VCPUS: 3,
406
    })
407

    
408
  _ModifyInstance(beparams={
409
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
410
    })
411

    
412
  _ModifyInstance(hvparams={
413
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
414
    })
415

    
416

    
417
def TestInterClusterInstanceMove(src_instance, dest_instance,
418
                                 pnode, snode, tnode):
419
  """Test tools/move-instance"""
420
  master = qa_config.GetMasterNode()
421

    
422
  rapi_pw_file = tempfile.NamedTemporaryFile()
423
  rapi_pw_file.write(_rapi_password)
424
  rapi_pw_file.flush()
425

    
426
  # TODO: Run some instance tests before moving back
427

    
428
  if snode is None:
429
    # instance is not redundant, but we still need to pass a node
430
    # (which will be ignored)
431
    fsec = tnode
432
  else:
433
    fsec = snode
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"])]:
440
    cmd = [
441
      "../tools/move-instance",
442
      "--verbose",
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,
450
      master["primary"],
451
      master["primary"],
452
      si,
453
      ]
454

    
455
    AssertEqual(StartLocalCommand(cmd).wait(), 0)