Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 4b10fb65

History | View | Annotate | Download (13.8 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
  "alloc_policy",
100
  "node_cnt", "node_list",
101
  ])
102

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

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

    
111

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

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

    
118

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

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

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

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

    
134
    results.append(data)
135

    
136
  return results
137

    
138

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

    
142

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

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

    
151

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

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

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

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

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

    
176
  def _VerifyGroups(data):
177
    default_group = {
178
      "name": constants.INITIAL_NODE_GROUP_NAME,
179
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
180
      }
181
    AssertIn(default_group, data)
182

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

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

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

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

    
219

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

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

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

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

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

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

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

    
260

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

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

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

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

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

    
284

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

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

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

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

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

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

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

    
320

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

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

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

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

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

    
339

    
340
def TestRapiNodeGroups():
341
  """Test several node group operations using RAPI.
342

343
  """
344
  groups = qa_config.get("groups", {})
345
  group1, group2, group3 = groups.get("inexistent-groups",
346
                                      ["group1", "group2", "group3"])[:3]
347

    
348
  # Create a group with no attributes
349
  body = {
350
    "name": group1,
351
    }
352

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

    
357
  _WaitForRapiJob(job_id)
358

    
359
  # Create a group specifying alloc_policy
360
  body = {
361
    "name": group2,
362
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
363
    }
364

    
365
  (job_id, ) = _DoTests([
366
    ("/2/groups", _VerifyReturnsJob, "POST", body),
367
    ])
368

    
369
  _WaitForRapiJob(job_id)
370

    
371
  # Modify alloc_policy
372
  body = {
373
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
374
    }
375

    
376
  (job_id, ) = _DoTests([
377
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
378
    ])
379

    
380
  _WaitForRapiJob(job_id)
381

    
382
  # Rename a group
383
  body = {
384
    "new_name": group3,
385
    }
386

    
387
  (job_id, ) = _DoTests([
388
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
389
    ])
390

    
391
  _WaitForRapiJob(job_id)
392

    
393
  # Delete groups
394
  for group in [group1, group3]:
395
    (job_id, ) = _DoTests([
396
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
397
      ])
398

    
399
    _WaitForRapiJob(job_id)
400

    
401

    
402
def TestRapiInstanceAdd(node, use_client):
403
  """Test adding a new instance via RAPI"""
404
  instance = qa_config.AcquireInstance()
405
  try:
406
    memory = utils.ParseUnit(qa_config.get("mem"))
407
    disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]
408

    
409
    if use_client:
410
      disks = [{"size": size} for size in disk_sizes]
411
      nics = [{}]
412

    
413
      beparams = {
414
        constants.BE_MEMORY: memory,
415
        }
416

    
417
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
418
                                           instance["name"],
419
                                           constants.DT_PLAIN,
420
                                           disks, nics,
421
                                           os=qa_config.get("os"),
422
                                           pnode=node["primary"],
423
                                           beparams=beparams)
424
    else:
425
      body = {
426
        "name": instance["name"],
427
        "os": qa_config.get("os"),
428
        "disk_template": constants.DT_PLAIN,
429
        "pnode": node["primary"],
430
        "memory": memory,
431
        "disks": disk_sizes,
432
        }
433

    
434
      (job_id, ) = _DoTests([
435
        ("/2/instances", _VerifyReturnsJob, "POST", body),
436
        ])
437

    
438
    _WaitForRapiJob(job_id)
439

    
440
    return instance
441
  except:
442
    qa_config.ReleaseInstance(instance)
443
    raise
444

    
445

    
446
def TestRapiInstanceRemove(instance, use_client):
447
  """Test removing instance via RAPI"""
448
  if use_client:
449
    job_id = _rapi_client.DeleteInstance(instance["name"])
450
  else:
451
    (job_id, ) = _DoTests([
452
      ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
453
      ])
454

    
455
  _WaitForRapiJob(job_id)
456

    
457
  qa_config.ReleaseInstance(instance)
458

    
459

    
460
def TestRapiInstanceMigrate(instance):
461
  """Test migrating instance via RAPI"""
462
  # Move to secondary node
463
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
464
  # And back to previous primary
465
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
466

    
467

    
468
def TestRapiInstanceRename(rename_source, rename_target):
469
  """Test renaming instance via RAPI"""
470
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
471

    
472

    
473
def TestRapiInstanceModify(instance):
474
  """Test modifying instance via RAPI"""
475
  def _ModifyInstance(**kwargs):
476
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
477

    
478
  _ModifyInstance(hvparams={
479
    constants.HV_KERNEL_ARGS: "single",
480
    })
481

    
482
  _ModifyInstance(beparams={
483
    constants.BE_VCPUS: 3,
484
    })
485

    
486
  _ModifyInstance(beparams={
487
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
488
    })
489

    
490
  _ModifyInstance(hvparams={
491
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
492
    })
493

    
494

    
495
def TestInterClusterInstanceMove(src_instance, dest_instance,
496
                                 pnode, snode, tnode):
497
  """Test tools/move-instance"""
498
  master = qa_config.GetMasterNode()
499

    
500
  rapi_pw_file = tempfile.NamedTemporaryFile()
501
  rapi_pw_file.write(_rapi_password)
502
  rapi_pw_file.flush()
503

    
504
  # TODO: Run some instance tests before moving back
505

    
506
  if snode is None:
507
    # instance is not redundant, but we still need to pass a node
508
    # (which will be ignored)
509
    fsec = tnode
510
  else:
511
    fsec = snode
512
  # note: pnode:snode are the *current* nodes, so we move it first to
513
  # tnode:pnode, then back to pnode:snode
514
  for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
515
                          tnode["primary"], pnode["primary"]),
516
                         (dest_instance["name"], src_instance["name"],
517
                          pnode["primary"], fsec["primary"])]:
518
    cmd = [
519
      "../tools/move-instance",
520
      "--verbose",
521
      "--src-ca-file=%s" % _rapi_ca.name,
522
      "--src-username=%s" % _rapi_username,
523
      "--src-password-file=%s" % rapi_pw_file.name,
524
      "--dest-instance-name=%s" % di,
525
      "--dest-primary-node=%s" % pn,
526
      "--dest-secondary-node=%s" % sn,
527
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
528
      master["primary"],
529
      master["primary"],
530
      si,
531
      ]
532

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