Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 0e265161

History | View | Annotate | Download (14.7 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
from ganeti import objects
33

    
34
import ganeti.rapi.client        # pylint: disable-msg=W0611
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, AssertIn, AssertMatch, StartLocalCommand)
42

    
43

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

    
49

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

53
  """
54
  # pylint: disable-msg=W0603
55
  # due to global usage
56
  global _rapi_ca
57
  global _rapi_client
58
  global _rapi_username
59
  global _rapi_password
60

    
61
  _rapi_username = username
62
  _rapi_password = password
63

    
64
  master = qa_config.GetMasterNode()
65

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

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

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

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

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

    
86

    
87
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
88
                   "admin_state",
89
                   "disk_template", "disk.sizes",
90
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
91
                   "beparams", "hvparams",
92
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
93

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

    
98
GROUP_FIELDS = frozenset([
99
  "name", "uuid",
100
  "alloc_policy",
101
  "node_cnt", "node_list",
102
  ])
103

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

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

    
112

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

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

    
119

    
120
def _DoTests(uris):
121
  # pylint: disable-msg=W0212
122
  # due to _SendRequest usage
123
  results = []
124

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

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

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

    
137
    results.append(data)
138

    
139
  return results
140

    
141

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

    
145

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

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

    
154

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

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

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

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

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

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

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

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

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

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

    
222

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

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

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

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

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

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

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

    
263

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

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

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

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

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

    
287

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

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

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

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

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

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

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

    
323

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

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

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

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

    
340

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

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

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

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

    
358
  _WaitForRapiJob(job_id)
359

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

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

    
370
  _WaitForRapiJob(job_id)
371

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

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

    
381
  _WaitForRapiJob(job_id)
382

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

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

    
392
  _WaitForRapiJob(job_id)
393

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

    
400
    _WaitForRapiJob(job_id)
401

    
402

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

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

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

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

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

    
439
    _WaitForRapiJob(job_id)
440

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

    
446

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

    
456
  _WaitForRapiJob(job_id)
457

    
458
  qa_config.ReleaseInstance(instance)
459

    
460

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

    
468

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

    
473

    
474
def TestRapiInstanceReinstall(instance):
475
  """Test reinstalling an instance via RAPI"""
476
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
477

    
478

    
479
def TestRapiInstanceModify(instance):
480
  """Test modifying instance via RAPI"""
481
  def _ModifyInstance(**kwargs):
482
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))
483

    
484
  _ModifyInstance(hvparams={
485
    constants.HV_KERNEL_ARGS: "single",
486
    })
487

    
488
  _ModifyInstance(beparams={
489
    constants.BE_VCPUS: 3,
490
    })
491

    
492
  _ModifyInstance(beparams={
493
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
494
    })
495

    
496
  _ModifyInstance(hvparams={
497
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
498
    })
499

    
500

    
501
def TestRapiInstanceConsole(instance):
502
  """Test getting instance console information via RAPI"""
503
  result = _rapi_client.GetInstanceConsole(instance["name"])
504
  console = objects.InstanceConsole.FromDict(result)
505
  AssertEqual(console.Validate(), True)
506
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance["name"]))
507

    
508

    
509
def TestRapiStoppedInstanceConsole(instance):
510
  """Test getting stopped instance's console information via RAPI"""
511
  try:
512
    _rapi_client.GetInstanceConsole(instance["name"])
513
  except rapi.client.GanetiApiError, err:
514
    AssertEqual(err.code, 503)
515
  else:
516
    raise qa_error.Error("Getting console for stopped instance didn't"
517
                         " return HTTP 503")
518

    
519

    
520
def TestInterClusterInstanceMove(src_instance, dest_instance,
521
                                 pnode, snode, tnode):
522
  """Test tools/move-instance"""
523
  master = qa_config.GetMasterNode()
524

    
525
  rapi_pw_file = tempfile.NamedTemporaryFile()
526
  rapi_pw_file.write(_rapi_password)
527
  rapi_pw_file.flush()
528

    
529
  # TODO: Run some instance tests before moving back
530

    
531
  if snode is None:
532
    # instance is not redundant, but we still need to pass a node
533
    # (which will be ignored)
534
    fsec = tnode
535
  else:
536
    fsec = snode
537
  # note: pnode:snode are the *current* nodes, so we move it first to
538
  # tnode:pnode, then back to pnode:snode
539
  for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
540
                          tnode["primary"], pnode["primary"]),
541
                         (dest_instance["name"], src_instance["name"],
542
                          pnode["primary"], fsec["primary"])]:
543
    cmd = [
544
      "../tools/move-instance",
545
      "--verbose",
546
      "--src-ca-file=%s" % _rapi_ca.name,
547
      "--src-username=%s" % _rapi_username,
548
      "--src-password-file=%s" % rapi_pw_file.name,
549
      "--dest-instance-name=%s" % di,
550
      "--dest-primary-node=%s" % pn,
551
      "--dest-secondary-node=%s" % sn,
552
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
553
      master["primary"],
554
      master["primary"],
555
      si,
556
      ]
557

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