Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 2237687b

History | View | Annotate | Download (13.8 kB)

1
#
2

    
3
# Copyright (C) 2007, 2008, 2009, 2010, 2011 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 cli
31
from ganeti import rapi
32

    
33
import ganeti.rapi.client        # pylint: disable-msg=W0611
34
import ganeti.rapi.client_utils
35

    
36
import qa_config
37
import qa_utils
38
import qa_error
39

    
40
from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
41

    
42

    
43
_rapi_ca = None
44
_rapi_client = None
45
_rapi_username = None
46
_rapi_password = None
47

    
48

    
49
def Setup(username, password):
50
  """Configures the RAPI client.
51

52
  """
53
  # pylint: disable-msg=W0603
54
  # due to global usage
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
  # pylint: disable-msg=W0212
121
  # due to _SendRequest usage
122
  results = []
123

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

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

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

    
136
    results.append(data)
137

    
138
  return results
139

    
140

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

    
144

    
145
def TestVersion():
146
  """Testing remote API version.
147

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

    
153

    
154
def TestEmptyCluster():
155
  """Testing remote API on an empty cluster.
156

157
  """
158
  master = qa_config.GetMasterNode()
159
  master_full = qa_utils.ResolveNodeName(master)
160

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

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

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

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

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

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

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

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

    
221

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

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

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

    
235
  def _VerifyInstancesBulk(data):
236
    for instance_data in data:
237
      _VerifyInstance(instance_data)
238

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

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

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

    
262

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

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

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

    
276
  def _VerifyNodesBulk(data):
277
    for node_data in data:
278
      _VerifyNode(node_data)
279

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

    
286

    
287
def TestTags(kind, name, tags):
288
  """Tests .../tags resources.
289

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

    
300
  def _VerifyTags(data):
301
    AssertEqual(sorted(tags), sorted(data))
302

    
303
  query = "&".join("tag=%s" % i for i in tags)
304

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

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

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

    
322

    
323
def _WaitForRapiJob(job_id):
324
  """Waits for a job to finish.
325

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)