Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 30131294

History | View | Annotate | Download (12.6 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
GROUP_FIELDS = frozenset([
98
  "name", "uuid",
99
  "node_cnt", "node_list",
100
  ])
101

    
102
JOB_FIELDS = frozenset([
103
  "id", "ops", "status", "summary",
104
  "opstatus", "opresult", "oplog",
105
  "received_ts", "start_ts", "end_ts",
106
  ])
107

    
108
LIST_FIELDS = ("id", "uri")
109

    
110

    
111
def Enabled():
112
  """Return whether remote API tests should be run.
113

114
  """
115
  return qa_config.TestEnabled('rapi')
116

    
117

    
118
def _DoTests(uris):
119
  results = []
120

    
121
  for uri, verify, method, body in uris:
122
    assert uri.startswith("/")
123

    
124
    print "%s %s" % (method, uri)
125
    data = _rapi_client._SendRequest(method, uri, None, body)
126

    
127
    if verify is not None:
128
      if callable(verify):
129
        verify(data)
130
      else:
131
        AssertEqual(data, verify)
132

    
133
    results.append(data)
134

    
135
  return results
136

    
137

    
138
def _VerifyReturnsJob(data):
139
  AssertMatch(data, r'^\d+$')
140

    
141

    
142
def TestVersion():
143
  """Testing remote API version.
144

145
  """
146
  _DoTests([
147
    ("/version", constants.RAPI_VERSION, 'GET', None),
148
    ])
149

    
150

    
151
def TestEmptyCluster():
152
  """Testing remote API on an empty cluster.
153

154
  """
155
  master = qa_config.GetMasterNode()
156
  master_full = qa_utils.ResolveNodeName(master)
157

    
158
  def _VerifyInfo(data):
159
    AssertIn("name", data)
160
    AssertIn("master", data)
161
    AssertEqual(data["master"], master_full)
162

    
163
  def _VerifyNodes(data):
164
    master_entry = {
165
      "id": master_full,
166
      "uri": "/2/nodes/%s" % master_full,
167
      }
168
    AssertIn(master_entry, data)
169

    
170
  def _VerifyNodesBulk(data):
171
    for node in data:
172
      for entry in NODE_FIELDS:
173
        AssertIn(entry, node)
174

    
175
  def _VerifyGroups(data):
176
    default_group = {
177
      "name": "default",
178
      "uri": "/2/groups/default",
179
      }
180
    AssertIn(default_group, data)
181

    
182
  def _VerifyGroupsBulk(data):
183
    for group in data:
184
      for field in GROUP_FIELDS:
185
        AssertIn(field, group)
186

    
187
  _DoTests([
188
    ("/", None, 'GET', None),
189
    ("/2/info", _VerifyInfo, 'GET', None),
190
    ("/2/tags", None, 'GET', None),
191
    ("/2/nodes", _VerifyNodes, 'GET', None),
192
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
193
    ("/2/groups", _VerifyGroups, 'GET', None),
194
    ("/2/groups?bulk=1", _VerifyGroupsBulk, 'GET', None),
195
    ("/2/instances", [], 'GET', None),
196
    ("/2/instances?bulk=1", [], 'GET', None),
197
    ("/2/os", None, 'GET', None),
198
    ])
199

    
200
  # Test HTTP Not Found
201
  for method in ["GET", "PUT", "POST", "DELETE"]:
202
    try:
203
      _DoTests([("/99/resource/not/here/99", None, method, None)])
204
    except rapi.client.GanetiApiError, err:
205
      AssertEqual(err.code, 404)
206
    else:
207
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
208

    
209
  # Test HTTP Not Implemented
210
  for method in ["PUT", "POST", "DELETE"]:
211
    try:
212
      _DoTests([("/version", None, method, None)])
213
    except rapi.client.GanetiApiError, err:
214
      AssertEqual(err.code, 501)
215
    else:
216
      raise qa_error.Error("Non-implemented method didn't fail")
217

    
218

    
219
def TestInstance(instance):
220
  """Testing getting instance(s) info via remote API.
221

222
  """
223
  def _VerifyInstance(data):
224
    for entry in INSTANCE_FIELDS:
225
      AssertIn(entry, data)
226

    
227
  def _VerifyInstancesList(data):
228
    for instance in data:
229
      for entry in LIST_FIELDS:
230
        AssertIn(entry, instance)
231

    
232
  def _VerifyInstancesBulk(data):
233
    for instance_data in data:
234
      _VerifyInstance(instance_data)
235

    
236
  _DoTests([
237
    ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
238
    ("/2/instances", _VerifyInstancesList, 'GET', None),
239
    ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
240
    ("/2/instances/%s/activate-disks" % instance["name"],
241
     _VerifyReturnsJob, 'PUT', None),
242
    ("/2/instances/%s/deactivate-disks" % instance["name"],
243
     _VerifyReturnsJob, 'PUT', None),
244
    ])
245

    
246
  # Test OpPrepareExport
247
  (job_id, ) = _DoTests([
248
    ("/2/instances/%s/prepare-export?mode=%s" %
249
     (instance["name"], constants.EXPORT_MODE_REMOTE),
250
     _VerifyReturnsJob, "PUT", None),
251
    ])
252

    
253
  result = _WaitForRapiJob(job_id)[0]
254
  AssertEqual(len(result["handshake"]), 3)
255
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
256
  AssertEqual(len(result["x509_key_name"]), 3)
257
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
258

    
259

    
260
def TestNode(node):
261
  """Testing getting node(s) info via remote API.
262

263
  """
264
  def _VerifyNode(data):
265
    for entry in NODE_FIELDS:
266
      AssertIn(entry, data)
267

    
268
  def _VerifyNodesList(data):
269
    for node in data:
270
      for entry in LIST_FIELDS:
271
        AssertIn(entry, node)
272

    
273
  def _VerifyNodesBulk(data):
274
    for node_data in data:
275
      _VerifyNode(node_data)
276

    
277
  _DoTests([
278
    ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
279
    ("/2/nodes", _VerifyNodesList, 'GET', None),
280
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
281
    ])
282

    
283

    
284
def TestTags(kind, name, tags):
285
  """Tests .../tags resources.
286

287
  """
288
  if kind == constants.TAG_CLUSTER:
289
    uri = "/2/tags"
290
  elif kind == constants.TAG_NODE:
291
    uri = "/2/nodes/%s/tags" % name
292
  elif kind == constants.TAG_INSTANCE:
293
    uri = "/2/instances/%s/tags" % name
294
  else:
295
    raise errors.ProgrammerError("Unknown tag kind")
296

    
297
  def _VerifyTags(data):
298
    AssertEqual(sorted(tags), sorted(data))
299

    
300
  query = "&".join("tag=%s" % i for i in tags)
301

    
302
  # Add tags
303
  (job_id, ) = _DoTests([
304
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
305
    ])
306
  _WaitForRapiJob(job_id)
307

    
308
  # Retrieve tags
309
  _DoTests([
310
    (uri, _VerifyTags, 'GET', None),
311
    ])
312

    
313
  # Remove tags
314
  (job_id, ) = _DoTests([
315
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
316
    ])
317
  _WaitForRapiJob(job_id)
318

    
319

    
320
def _WaitForRapiJob(job_id):
321
  """Waits for a job to finish.
322

323
  """
324
  master = qa_config.GetMasterNode()
325

    
326
  def _VerifyJob(data):
327
    AssertEqual(data["id"], job_id)
328
    for field in JOB_FIELDS:
329
      AssertIn(field, data)
330

    
331
  _DoTests([
332
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
333
    ])
334

    
335
  return rapi.client_utils.PollJob(_rapi_client, job_id,
336
                                   cli.StdioJobPollReportCb())
337

    
338

    
339
def TestRapiInstanceAdd(node, use_client):
340
  """Test adding a new instance via RAPI"""
341
  instance = qa_config.AcquireInstance()
342
  try:
343
    memory = utils.ParseUnit(qa_config.get("mem"))
344
    disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
345

    
346
    if use_client:
347
      disks = [{"size": size} for size in disk_sizes]
348
      nics = [{}]
349

    
350
      beparams = {
351
        constants.BE_MEMORY: memory,
352
        }
353

    
354
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
355
                                           instance["name"],
356
                                           constants.DT_PLAIN,
357
                                           disks, nics,
358
                                           os=qa_config.get("os"),
359
                                           pnode=node["primary"],
360
                                           beparams=beparams)
361
    else:
362
      body = {
363
        "name": instance["name"],
364
        "os": qa_config.get("os"),
365
        "disk_template": constants.DT_PLAIN,
366
        "pnode": node["primary"],
367
        "memory": memory,
368
        "disks": disk_sizes,
369
        }
370

    
371
      (job_id, ) = _DoTests([
372
        ("/2/instances", _VerifyReturnsJob, "POST", body),
373
        ])
374

    
375
    _WaitForRapiJob(job_id)
376

    
377
    return instance
378
  except:
379
    qa_config.ReleaseInstance(instance)
380
    raise
381

    
382

    
383
def TestRapiInstanceRemove(instance, use_client):
384
  """Test removing instance via RAPI"""
385
  if use_client:
386
    job_id = _rapi_client.DeleteInstance(instance["name"])
387
  else:
388
    (job_id, ) = _DoTests([
389
      ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
390
      ])
391

    
392
  _WaitForRapiJob(job_id)
393

    
394
  qa_config.ReleaseInstance(instance)
395

    
396

    
397
def TestRapiInstanceMigrate(instance):
398
  """Test migrating instance via RAPI"""
399
  # Move to secondary node
400
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
401
  # And back to previous primary
402
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
403

    
404

    
405
def TestRapiInstanceRename(instance, rename_target):
406
  """Test renaming instance via RAPI"""
407
  rename_source = instance["name"]
408

    
409
  for name1, name2 in [(rename_source, rename_target),
410
                       (rename_target, rename_source)]:
411
    _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))
412

    
413

    
414
def TestRapiInstanceModify(instance):
415
  """Test modifying instance via RAPI"""
416
  def _ModifyInstance(**kwargs):
417
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
418

    
419
  _ModifyInstance(hvparams={
420
    constants.HV_KERNEL_ARGS: "single",
421
    })
422

    
423
  _ModifyInstance(beparams={
424
    constants.BE_VCPUS: 3,
425
    })
426

    
427
  _ModifyInstance(beparams={
428
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
429
    })
430

    
431
  _ModifyInstance(hvparams={
432
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
433
    })
434

    
435

    
436
def TestInterClusterInstanceMove(src_instance, dest_instance,
437
                                 pnode, snode, tnode):
438
  """Test tools/move-instance"""
439
  master = qa_config.GetMasterNode()
440

    
441
  rapi_pw_file = tempfile.NamedTemporaryFile()
442
  rapi_pw_file.write(_rapi_password)
443
  rapi_pw_file.flush()
444

    
445
  # TODO: Run some instance tests before moving back
446

    
447
  if snode is None:
448
    # instance is not redundant, but we still need to pass a node
449
    # (which will be ignored)
450
    fsec = tnode
451
  else:
452
    fsec = snode
453
  # note: pnode:snode are the *current* nodes, so we move it first to
454
  # tnode:pnode, then back to pnode:snode
455
  for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
456
                          tnode["primary"], pnode["primary"]),
457
                         (dest_instance["name"], src_instance["name"],
458
                          pnode["primary"], fsec["primary"])]:
459
    cmd = [
460
      "../tools/move-instance",
461
      "--verbose",
462
      "--src-ca-file=%s" % _rapi_ca.name,
463
      "--src-username=%s" % _rapi_username,
464
      "--src-password-file=%s" % rapi_pw_file.name,
465
      "--dest-instance-name=%s" % di,
466
      "--dest-primary-node=%s" % pn,
467
      "--dest-secondary-node=%s" % sn,
468
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
469
      master["primary"],
470
      master["primary"],
471
      si,
472
      ]
473

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