Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ da235ee4

History | View | Annotate | Download (30.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Remote API QA tests.
23

24
"""
25

    
26
import functools
27
import itertools
28
import random
29
import re
30
import tempfile
31

    
32
from ganeti import cli
33
from ganeti import compat
34
from ganeti import constants
35
from ganeti import errors
36
from ganeti import pathutils
37
from ganeti import objects
38
from ganeti import opcodes
39
from ganeti import query
40
from ganeti import qlang
41
from ganeti import rapi
42
from ganeti import utils
43

    
44
import ganeti.rapi.client        # pylint: disable=W0611
45
import ganeti.rapi.client_utils
46

    
47
import qa_config
48
import qa_error
49
import qa_logging
50
import qa_utils
51

    
52
from qa_instance import IsFailoverSupported
53
from qa_instance import IsMigrationSupported
54
from qa_instance import IsDiskReplacingSupported
55
from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
56
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG
57

    
58

    
59
_rapi_ca = None
60
_rapi_client = None
61
_rapi_username = None
62
_rapi_password = None
63

    
64

    
65
def Setup(username, password):
66
  """Configures the RAPI client.
67

68
  """
69
  # pylint: disable=W0603
70
  # due to global usage
71
  global _rapi_ca
72
  global _rapi_client
73
  global _rapi_username
74
  global _rapi_password
75

    
76
  _rapi_username = username
77
  _rapi_password = password
78

    
79
  master = qa_config.GetMasterNode()
80

    
81
  # Load RAPI certificate from master node
82
  cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_CERT_FILE)]
83

    
84
  # Write to temporary file
85
  _rapi_ca = tempfile.NamedTemporaryFile()
86
  _rapi_ca.write(qa_utils.GetCommandOutput(master.primary,
87
                                           utils.ShellQuoteArgs(cmd)))
88
  _rapi_ca.flush()
89

    
90
  port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
91
  cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
92
                                           proxy="")
93

    
94
  if qa_config.UseVirtualCluster():
95
    # TODO: Implement full support for RAPI on virtual clusters
96
    print qa_logging.FormatWarning("RAPI tests are not yet supported on"
97
                                   " virtual clusters and will be disabled")
98

    
99
    assert _rapi_client is None
100
  else:
101
    _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port,
102
                                                username=username,
103
                                                password=password,
104
                                                curl_config_fn=cfg_curl)
105

    
106
    print "RAPI protocol version: %s" % _rapi_client.GetVersion()
107

    
108

    
109
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
110
                   "admin_state",
111
                   "disk_template", "disk.sizes", "disk.spindles",
112
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
113
                   "beparams", "hvparams",
114
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
115

    
116
NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
117
               "mtotal", "mnode", "mfree",
118
               "pinst_cnt", "sinst_cnt", "tags")
119

    
120
GROUP_FIELDS = compat.UniqueFrozenset([
121
  "name", "uuid",
122
  "alloc_policy",
123
  "node_cnt", "node_list",
124
  ])
125

    
126
JOB_FIELDS = compat.UniqueFrozenset([
127
  "id", "ops", "status", "summary",
128
  "opstatus", "opresult", "oplog",
129
  "received_ts", "start_ts", "end_ts",
130
  ])
131

    
132
LIST_FIELDS = ("id", "uri")
133

    
134

    
135
def Enabled():
136
  """Return whether remote API tests should be run.
137

138
  """
139
  # TODO: Implement RAPI tests for virtual clusters
140
  return (qa_config.TestEnabled("rapi") and
141
          not qa_config.UseVirtualCluster())
142

    
143

    
144
def _DoTests(uris):
145
  # pylint: disable=W0212
146
  # due to _SendRequest usage
147
  results = []
148

    
149
  for uri, verify, method, body in uris:
150
    assert uri.startswith("/")
151

    
152
    print "%s %s" % (method, uri)
153
    data = _rapi_client._SendRequest(method, uri, None, body)
154

    
155
    if verify is not None:
156
      if callable(verify):
157
        verify(data)
158
      else:
159
        AssertEqual(data, verify)
160

    
161
    results.append(data)
162

    
163
  return results
164

    
165

    
166
# pylint: disable=W0212
167
# Due to _SendRequest usage
168
def _DoGetPutTests(get_uri, modify_uri, opcode_params, rapi_only_aliases=None,
169
                   modify_method="PUT", exceptions=None, set_exceptions=None):
170
  """ Test if all params of an object can be retrieved, and set as well.
171

172
  @type get_uri: string
173
  @param get_uri: The URI from which information about the object can be
174
                  retrieved.
175
  @type modify_uri: string
176
  @param modify_uri: The URI which can be used to modify the object.
177
  @type opcode_params: list of tuple
178
  @param opcode_params: The parameters of the underlying opcode, used to
179
                        determine which parameters are actually present.
180
  @type rapi_only_aliases: list of string or None
181
  @param rapi_only_aliases: Aliases for parameters which differ from the opcode,
182
                            and become renamed before opcode submission.
183
  @type modify_method: string
184
  @param modify_method: The method to be used in the modification.
185
  @type exceptions: list of string or None
186
  @param exceptions: The parameters which have not been exposed and should not
187
                     be tested at all.
188
  @type set_exceptions: list of string or None
189
  @param set_exceptions: The parameters whose setting should not be tested as a
190
                         part of this test.
191

192
  """
193

    
194
  assert get_uri.startswith("/")
195
  assert modify_uri.startswith("/")
196

    
197
  if exceptions is None:
198
    exceptions = []
199
  if set_exceptions is None:
200
    set_exceptions = []
201

    
202
  print "Testing get/modify symmetry of %s and %s" % (get_uri, modify_uri)
203

    
204
  # First we see if all parameters of the opcode are returned through RAPI
205
  params_of_interest = map(lambda x: x[0], opcode_params)
206

    
207
  # The RAPI-specific aliases are to be checked as well
208
  if rapi_only_aliases is not None:
209
    params_of_interest.extend(rapi_only_aliases)
210

    
211
  info = _rapi_client._SendRequest("GET", get_uri, None, {})
212

    
213
  missing_params = filter(lambda x: x not in info and x not in exceptions,
214
                          params_of_interest)
215
  if missing_params:
216
    raise qa_error.Error("The parameters %s which can be set through the "
217
                         "appropriate opcode are not present in the response "
218
                         "from %s" % (','.join(missing_params), get_uri))
219

    
220
  print "GET successful at %s" % get_uri
221

    
222
  # Then if we can perform a set with the same values as received
223
  put_payload = {}
224
  for param in params_of_interest:
225
    if param not in exceptions and param not in set_exceptions:
226
      put_payload[param] = info[param]
227

    
228
  _rapi_client._SendRequest(modify_method, modify_uri, None, put_payload)
229

    
230
  print "%s successful at %s" % (modify_method, modify_uri)
231
# pylint: enable=W0212
232

    
233

    
234
def _VerifyReturnsJob(data):
235
  if not isinstance(data, int):
236
    AssertMatch(data, r"^\d+$")
237

    
238

    
239
def TestVersion():
240
  """Testing remote API version.
241

242
  """
243
  _DoTests([
244
    ("/version", constants.RAPI_VERSION, "GET", None),
245
    ])
246

    
247

    
248
def TestEmptyCluster():
249
  """Testing remote API on an empty cluster.
250

251
  """
252
  master = qa_config.GetMasterNode()
253
  master_full = qa_utils.ResolveNodeName(master)
254

    
255
  def _VerifyInfo(data):
256
    AssertIn("name", data)
257
    AssertIn("master", data)
258
    AssertEqual(data["master"], master_full)
259

    
260
  def _VerifyNodes(data):
261
    master_entry = {
262
      "id": master_full,
263
      "uri": "/2/nodes/%s" % master_full,
264
      }
265
    AssertIn(master_entry, data)
266

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

    
272
  def _VerifyGroups(data):
273
    default_group = {
274
      "name": constants.INITIAL_NODE_GROUP_NAME,
275
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
276
      }
277
    AssertIn(default_group, data)
278

    
279
  def _VerifyGroupsBulk(data):
280
    for group in data:
281
      for field in GROUP_FIELDS:
282
        AssertIn(field, group)
283

    
284
  _DoTests([
285
    ("/", None, "GET", None),
286
    ("/2/info", _VerifyInfo, "GET", None),
287
    ("/2/tags", None, "GET", None),
288
    ("/2/nodes", _VerifyNodes, "GET", None),
289
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
290
    ("/2/groups", _VerifyGroups, "GET", None),
291
    ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
292
    ("/2/instances", [], "GET", None),
293
    ("/2/instances?bulk=1", [], "GET", None),
294
    ("/2/os", None, "GET", None),
295
    ])
296

    
297
  # Test HTTP Not Found
298
  for method in ["GET", "PUT", "POST", "DELETE"]:
299
    try:
300
      _DoTests([("/99/resource/not/here/99", None, method, None)])
301
    except rapi.client.GanetiApiError, err:
302
      AssertEqual(err.code, 404)
303
    else:
304
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
305

    
306
  # Test HTTP Not Implemented
307
  for method in ["PUT", "POST", "DELETE"]:
308
    try:
309
      _DoTests([("/version", None, method, None)])
310
    except rapi.client.GanetiApiError, err:
311
      AssertEqual(err.code, 501)
312
    else:
313
      raise qa_error.Error("Non-implemented method didn't fail")
314

    
315
  # Test GET/PUT symmetry
316
  LEGITIMATELY_MISSING = [
317
    "force",       # Standard option
318
    "add_uids",    # Modifies UID pool, is not a param itself
319
    "remove_uids", # Same as above
320
  ]
321
  NOT_EXPOSED_YET = ["hv_state", "disk_state", "modify_etc_hosts"]
322
  # The nicparams are returned under the default entry, yet accepted as they
323
  # are - this is a TODO to fix!
324
  DEFAULT_ISSUES = ["nicparams"]
325

    
326
  _DoGetPutTests("/2/info", "/2/modify", opcodes.OpClusterSetParams.OP_PARAMS,
327
                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET),
328
                 set_exceptions=DEFAULT_ISSUES)
329

    
330

    
331
def TestRapiQuery():
332
  """Testing resource queries via remote API.
333

334
  """
335
  # FIXME: the tests are failing if no LVM is enabled, investigate
336
  # if it is a bug in the QA or in the code
337
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
338
    return
339

    
340
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
341
  rnd = random.Random(7818)
342

    
343
  for what in constants.QR_VIA_RAPI:
344
    if what == constants.QR_JOB:
345
      namefield = "id"
346
    elif what == constants.QR_EXPORT:
347
      namefield = "export"
348
    else:
349
      namefield = "name"
350

    
351
    all_fields = query.ALL_FIELDS[what].keys()
352
    rnd.shuffle(all_fields)
353

    
354
    # No fields, should return everything
355
    result = _rapi_client.QueryFields(what)
356
    qresult = objects.QueryFieldsResponse.FromDict(result)
357
    AssertEqual(len(qresult.fields), len(all_fields))
358

    
359
    # One field
360
    result = _rapi_client.QueryFields(what, fields=[namefield])
361
    qresult = objects.QueryFieldsResponse.FromDict(result)
362
    AssertEqual(len(qresult.fields), 1)
363

    
364
    # Specify all fields, order must be correct
365
    result = _rapi_client.QueryFields(what, fields=all_fields)
366
    qresult = objects.QueryFieldsResponse.FromDict(result)
367
    AssertEqual(len(qresult.fields), len(all_fields))
368
    AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
369

    
370
    # Unknown field
371
    result = _rapi_client.QueryFields(what, fields=["_unknown!"])
372
    qresult = objects.QueryFieldsResponse.FromDict(result)
373
    AssertEqual(len(qresult.fields), 1)
374
    AssertEqual(qresult.fields[0].name, "_unknown!")
375
    AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
376

    
377
    # Try once more, this time without the client
378
    _DoTests([
379
      ("/2/query/%s/fields" % what, None, "GET", None),
380
      ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
381
       None, "GET", None),
382
      ])
383

    
384
    # Try missing query argument
385
    try:
386
      _DoTests([
387
        ("/2/query/%s" % what, None, "GET", None),
388
        ])
389
    except rapi.client.GanetiApiError, err:
390
      AssertEqual(err.code, 400)
391
    else:
392
      raise qa_error.Error("Request missing 'fields' parameter didn't fail")
393

    
394
    def _Check(exp_fields, data):
395
      qresult = objects.QueryResponse.FromDict(data)
396
      AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
397
      if not isinstance(qresult.data, list):
398
        raise qa_error.Error("Query did not return a list")
399

    
400
    _DoTests([
401
      # Specify fields in query
402
      ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
403
       compat.partial(_Check, all_fields), "GET", None),
404

    
405
      ("/2/query/%s?fields=%s" % (what, namefield),
406
       compat.partial(_Check, [namefield]), "GET", None),
407

    
408
      # Note the spaces
409
      ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
410
       (what, namefield, namefield, namefield),
411
       compat.partial(_Check, [namefield] * 3), "GET", None),
412

    
413
      # PUT with fields in query
414
      ("/2/query/%s?fields=%s" % (what, namefield),
415
       compat.partial(_Check, [namefield]), "PUT", {}),
416

    
417
      ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
418
         "fields": [namefield] * 4,
419
         }),
420

    
421
      ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
422
         "fields": all_fields,
423
         }),
424

    
425
      ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
426
         "fields": [namefield] * 4
427
         })])
428

    
429
    def _CheckFilter():
430
      _DoTests([
431
        # With filter
432
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
433
           "fields": all_fields,
434
           "filter": [qlang.OP_TRUE, namefield],
435
           }),
436
        ])
437

    
438
    if what == constants.QR_LOCK:
439
      # Locks can't be filtered
440
      try:
441
        _CheckFilter()
442
      except rapi.client.GanetiApiError, err:
443
        AssertEqual(err.code, 500)
444
      else:
445
        raise qa_error.Error("Filtering locks didn't fail")
446
    else:
447
      _CheckFilter()
448

    
449
    if what == constants.QR_NODE:
450
      # Test with filter
451
      (nodes, ) = _DoTests(
452
        [("/2/query/%s" % what,
453
          compat.partial(_Check, ["name", "master"]), "PUT",
454
          {"fields": ["name", "master"],
455
           "filter": [qlang.OP_TRUE, "master"],
456
           })])
457
      qresult = objects.QueryResponse.FromDict(nodes)
458
      AssertEqual(qresult.data, [
459
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
460
        ])
461

    
462

    
463
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
464
def TestInstance(instance):
465
  """Testing getting instance(s) info via remote API.
466

467
  """
468
  def _VerifyInstance(data):
469
    for entry in INSTANCE_FIELDS:
470
      AssertIn(entry, data)
471

    
472
  def _VerifyInstancesList(data):
473
    for instance in data:
474
      for entry in LIST_FIELDS:
475
        AssertIn(entry, instance)
476

    
477
  def _VerifyInstancesBulk(data):
478
    for instance_data in data:
479
      _VerifyInstance(instance_data)
480

    
481
  _DoTests([
482
    ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
483
    ("/2/instances", _VerifyInstancesList, "GET", None),
484
    ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
485
    ("/2/instances/%s/activate-disks" % instance.name,
486
     _VerifyReturnsJob, "PUT", None),
487
    ("/2/instances/%s/deactivate-disks" % instance.name,
488
     _VerifyReturnsJob, "PUT", None),
489
    ])
490

    
491
  # Test OpBackupPrepare
492
  (job_id, ) = _DoTests([
493
    ("/2/instances/%s/prepare-export?mode=%s" %
494
     (instance.name, constants.EXPORT_MODE_REMOTE),
495
     _VerifyReturnsJob, "PUT", None),
496
    ])
497

    
498
  result = _WaitForRapiJob(job_id)[0]
499
  AssertEqual(len(result["handshake"]), 3)
500
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
501
  AssertEqual(len(result["x509_key_name"]), 3)
502
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
503

    
504

    
505
def TestNode(node):
506
  """Testing getting node(s) info via remote API.
507

508
  """
509
  def _VerifyNode(data):
510
    for entry in NODE_FIELDS:
511
      AssertIn(entry, data)
512

    
513
  def _VerifyNodesList(data):
514
    for node in data:
515
      for entry in LIST_FIELDS:
516
        AssertIn(entry, node)
517

    
518
  def _VerifyNodesBulk(data):
519
    for node_data in data:
520
      _VerifyNode(node_data)
521

    
522
  _DoTests([
523
    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
524
    ("/2/nodes", _VerifyNodesList, "GET", None),
525
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
526
    ])
527

    
528
  # Not parameters of the node, but controlling opcode behavior
529
  LEGITIMATELY_MISSING = ["force", "powered"]
530
  # Identifying the node - RAPI provides these itself
531
  IDENTIFIERS = ["node_name", "node_uuid"]
532
  # As the name states, these can be set but not retrieved yet
533
  NOT_EXPOSED_YET = ["hv_state", "disk_state", "auto_promote"]
534

    
535
  _DoGetPutTests("/2/nodes/%s" % node.primary,
536
                 "/2/nodes/%s/modify" % node.primary,
537
                 opcodes.OpNodeSetParams.OP_PARAMS,
538
                 modify_method="POST",
539
                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET +
540
                             IDENTIFIERS))
541

    
542

    
543
def _FilterTags(seq):
544
  """Removes unwanted tags from a sequence.
545

546
  """
547
  ignore_re = qa_config.get("ignore-tags-re", None)
548

    
549
  if ignore_re:
550
    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
551
  else:
552
    return seq
553

    
554

    
555
def TestTags(kind, name, tags):
556
  """Tests .../tags resources.
557

558
  """
559
  if kind == constants.TAG_CLUSTER:
560
    uri = "/2/tags"
561
  elif kind == constants.TAG_NODE:
562
    uri = "/2/nodes/%s/tags" % name
563
  elif kind == constants.TAG_INSTANCE:
564
    uri = "/2/instances/%s/tags" % name
565
  elif kind == constants.TAG_NODEGROUP:
566
    uri = "/2/groups/%s/tags" % name
567
  elif kind == constants.TAG_NETWORK:
568
    uri = "/2/networks/%s/tags" % name
569
  else:
570
    raise errors.ProgrammerError("Unknown tag kind")
571

    
572
  def _VerifyTags(data):
573
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
574

    
575
  queryargs = "&".join("tag=%s" % i for i in tags)
576

    
577
  # Add tags
578
  (job_id, ) = _DoTests([
579
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
580
    ])
581
  _WaitForRapiJob(job_id)
582

    
583
  # Retrieve tags
584
  _DoTests([
585
    (uri, _VerifyTags, "GET", None),
586
    ])
587

    
588
  # Remove tags
589
  (job_id, ) = _DoTests([
590
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
591
    ])
592
  _WaitForRapiJob(job_id)
593

    
594

    
595
def _WaitForRapiJob(job_id):
596
  """Waits for a job to finish.
597

598
  """
599
  def _VerifyJob(data):
600
    AssertEqual(data["id"], job_id)
601
    for field in JOB_FIELDS:
602
      AssertIn(field, data)
603

    
604
  _DoTests([
605
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
606
    ])
607

    
608
  return rapi.client_utils.PollJob(_rapi_client, job_id,
609
                                   cli.StdioJobPollReportCb())
610

    
611

    
612
def TestRapiNodeGroups():
613
  """Test several node group operations using RAPI.
614

615
  """
616
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
617

    
618
  # Create a group with no attributes
619
  body = {
620
    "name": group1,
621
    }
622

    
623
  (job_id, ) = _DoTests([
624
    ("/2/groups", _VerifyReturnsJob, "POST", body),
625
    ])
626

    
627
  _WaitForRapiJob(job_id)
628

    
629
  # Create a group specifying alloc_policy
630
  body = {
631
    "name": group2,
632
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
633
    }
634

    
635
  (job_id, ) = _DoTests([
636
    ("/2/groups", _VerifyReturnsJob, "POST", body),
637
    ])
638

    
639
  _WaitForRapiJob(job_id)
640

    
641
  # Modify alloc_policy
642
  body = {
643
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
644
    }
645

    
646
  (job_id, ) = _DoTests([
647
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
648
    ])
649

    
650
  _WaitForRapiJob(job_id)
651

    
652
  # Rename a group
653
  body = {
654
    "new_name": group3,
655
    }
656

    
657
  (job_id, ) = _DoTests([
658
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
659
    ])
660

    
661
  _WaitForRapiJob(job_id)
662

    
663
  # Delete groups
664
  for group in [group1, group3]:
665
    (job_id, ) = _DoTests([
666
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
667
      ])
668

    
669
    _WaitForRapiJob(job_id)
670

    
671

    
672
def TestRapiInstanceAdd(node, use_client):
673
  """Test adding a new instance via RAPI"""
674
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
675
    return
676
  instance = qa_config.AcquireInstance()
677
  instance.SetDiskTemplate(constants.DT_PLAIN)
678
  try:
679
    disks = [{"size": utils.ParseUnit(d.get("size")),
680
              "name": str(d.get("name"))}
681
             for d in qa_config.GetDiskOptions()]
682
    nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
683
    nics = [{
684
      constants.INIC_MAC: nic0_mac,
685
      }]
686

    
687
    beparams = {
688
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
689
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
690
      }
691

    
692
    if use_client:
693
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
694
                                           instance.name,
695
                                           constants.DT_PLAIN,
696
                                           disks, nics,
697
                                           os=qa_config.get("os"),
698
                                           pnode=node.primary,
699
                                           beparams=beparams)
700
    else:
701
      body = {
702
        "__version__": 1,
703
        "mode": constants.INSTANCE_CREATE,
704
        "name": instance.name,
705
        "os_type": qa_config.get("os"),
706
        "disk_template": constants.DT_PLAIN,
707
        "pnode": node.primary,
708
        "beparams": beparams,
709
        "disks": disks,
710
        "nics": nics,
711
        }
712

    
713
      (job_id, ) = _DoTests([
714
        ("/2/instances", _VerifyReturnsJob, "POST", body),
715
        ])
716

    
717
    _WaitForRapiJob(job_id)
718

    
719
    return instance
720
  except:
721
    instance.Release()
722
    raise
723

    
724

    
725
def _GenInstanceAllocationDict(node, instance):
726
  """Creates an instance allocation dict to be used with the RAPI"""
727
  instance.SetDiskTemplate(constants.DT_PLAIN)
728

    
729
  disks = [{"size": utils.ParseUnit(d.get("size")),
730
              "name": str(d.get("name"))}
731
             for d in qa_config.GetDiskOptions()]
732

    
733
  nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
734
  nics = [{
735
    constants.INIC_MAC: nic0_mac,
736
    }]
737

    
738
  beparams = {
739
    constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
740
    constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
741
    }
742

    
743
  return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
744
                                         instance.name,
745
                                         constants.DT_PLAIN,
746
                                         disks, nics,
747
                                         os=qa_config.get("os"),
748
                                         pnode=node.primary,
749
                                         beparams=beparams)
750

    
751

    
752
def TestRapiInstanceMultiAlloc(node):
753
  """Test adding two new instances via the RAPI instance-multi-alloc method"""
754
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
755
    return
756

    
757
  JOBS_KEY = "jobs"
758

    
759
  instance_one = qa_config.AcquireInstance()
760
  instance_two = qa_config.AcquireInstance()
761
  instance_list = [instance_one, instance_two]
762
  try:
763
    rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
764
                     instance_list)
765

    
766
    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
767

    
768
    results, = _WaitForRapiJob(job_id)
769

    
770
    if JOBS_KEY not in results:
771
      raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
772
                           "information about created jobs")
773

    
774
    if len(results[JOBS_KEY]) != len(instance_list):
775
      raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
776
                           "desired number of jobs!")
777

    
778
    for success, job in results[JOBS_KEY]:
779
      if success:
780
        _WaitForRapiJob(job)
781
      else:
782
        raise qa_error.Error("Failed to create instance in "
783
                             "instance-multi-alloc call")
784
  except:
785
    # Note that although released, it may be that some of the instance creations
786
    # have in fact succeeded. Handling this in a better way may be possible, but
787
    # is not necessary as the QA has already failed at this point.
788
    for instance in instance_list:
789
      instance.Release()
790
    raise
791

    
792
  return (instance_one, instance_two)
793

    
794

    
795
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
796
def TestRapiInstanceRemove(instance, use_client):
797
  """Test removing instance via RAPI"""
798
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
799
  # in RAPI or in the test
800
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
801
    return
802

    
803
  if use_client:
804
    job_id = _rapi_client.DeleteInstance(instance.name)
805
  else:
806
    (job_id, ) = _DoTests([
807
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
808
      ])
809

    
810
  _WaitForRapiJob(job_id)
811

    
812

    
813
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
814
def TestRapiInstanceMigrate(instance):
815
  """Test migrating instance via RAPI"""
816
  if not IsMigrationSupported(instance):
817
    print qa_logging.FormatInfo("Instance doesn't support migration, skipping"
818
                                " test")
819
    return
820
  # Move to secondary node
821
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
822
  qa_utils.RunInstanceCheck(instance, True)
823
  # And back to previous primary
824
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
825

    
826

    
827
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
828
def TestRapiInstanceFailover(instance):
829
  """Test failing over instance via RAPI"""
830
  if not IsFailoverSupported(instance):
831
    print qa_logging.FormatInfo("Instance doesn't support failover, skipping"
832
                                " test")
833
    return
834
  # Move to secondary node
835
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
836
  qa_utils.RunInstanceCheck(instance, True)
837
  # And back to previous primary
838
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
839

    
840

    
841
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
842
def TestRapiInstanceShutdown(instance):
843
  """Test stopping an instance via RAPI"""
844
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
845

    
846

    
847
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
848
def TestRapiInstanceStartup(instance):
849
  """Test starting an instance via RAPI"""
850
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
851

    
852

    
853
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
854
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
855
  """Test renaming instance via RAPI
856

857
  This must leave the instance with the original name (in the
858
  non-failure case).
859

860
  """
861
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
862
  qa_utils.RunInstanceCheck(rename_source, False)
863
  qa_utils.RunInstanceCheck(rename_target, False)
864
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
865
  qa_utils.RunInstanceCheck(rename_target, False)
866

    
867

    
868
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
869
def TestRapiInstanceReinstall(instance):
870
  """Test reinstalling an instance via RAPI"""
871
  if instance.disk_template == constants.DT_DISKLESS:
872
    print qa_logging.FormatInfo("Test not supported for diskless instances")
873
    return
874

    
875
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
876
  # By default, the instance is started again
877
  qa_utils.RunInstanceCheck(instance, True)
878

    
879
  # Reinstall again without starting
880
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
881
                                                 no_startup=True))
882

    
883

    
884
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
885
def TestRapiInstanceReplaceDisks(instance):
886
  """Test replacing instance disks via RAPI"""
887
  if not IsDiskReplacingSupported(instance):
888
    print qa_logging.FormatInfo("Instance doesn't support disk replacing,"
889
                                " skipping test")
890
    return
891
  fn = _rapi_client.ReplaceInstanceDisks
892
  _WaitForRapiJob(fn(instance.name,
893
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
894
  _WaitForRapiJob(fn(instance.name,
895
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
896

    
897

    
898
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
899
def TestRapiInstanceModify(instance):
900
  """Test modifying instance via RAPI"""
901
  default_hv = qa_config.GetDefaultHypervisor()
902

    
903
  def _ModifyInstance(**kwargs):
904
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
905

    
906
  _ModifyInstance(beparams={
907
    constants.BE_VCPUS: 3,
908
    })
909

    
910
  _ModifyInstance(beparams={
911
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
912
    })
913

    
914
  if default_hv == constants.HT_XEN_PVM:
915
    _ModifyInstance(hvparams={
916
      constants.HV_KERNEL_ARGS: "single",
917
      })
918
    _ModifyInstance(hvparams={
919
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
920
      })
921
  elif default_hv == constants.HT_XEN_HVM:
922
    _ModifyInstance(hvparams={
923
      constants.HV_BOOT_ORDER: "acn",
924
      })
925
    _ModifyInstance(hvparams={
926
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
927
      })
928

    
929

    
930
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
931
def TestRapiInstanceConsole(instance):
932
  """Test getting instance console information via RAPI"""
933
  result = _rapi_client.GetInstanceConsole(instance.name)
934
  console = objects.InstanceConsole.FromDict(result)
935
  AssertEqual(console.Validate(), True)
936
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
937

    
938

    
939
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
940
def TestRapiStoppedInstanceConsole(instance):
941
  """Test getting stopped instance's console information via RAPI"""
942
  try:
943
    _rapi_client.GetInstanceConsole(instance.name)
944
  except rapi.client.GanetiApiError, err:
945
    AssertEqual(err.code, 503)
946
  else:
947
    raise qa_error.Error("Getting console for stopped instance didn't"
948
                         " return HTTP 503")
949

    
950

    
951
def GetOperatingSystems():
952
  """Retrieves a list of all available operating systems.
953

954
  """
955
  return _rapi_client.GetOperatingSystems()
956

    
957

    
958
def TestInterClusterInstanceMove(src_instance, dest_instance,
959
                                 inodes, tnode):
960
  """Test tools/move-instance"""
961
  master = qa_config.GetMasterNode()
962

    
963
  rapi_pw_file = tempfile.NamedTemporaryFile()
964
  rapi_pw_file.write(_rapi_password)
965
  rapi_pw_file.flush()
966

    
967
  dest_instance.SetDiskTemplate(src_instance.disk_template)
968

    
969
  # TODO: Run some instance tests before moving back
970

    
971
  if len(inodes) > 1:
972
    # No disk template currently requires more than 1 secondary node. If this
973
    # changes, either this test must be skipped or the script must be updated.
974
    assert len(inodes) == 2
975
    snode = inodes[1]
976
  else:
977
    # instance is not redundant, but we still need to pass a node
978
    # (which will be ignored)
979
    snode = tnode
980
  pnode = inodes[0]
981
  # note: pnode:snode are the *current* nodes, so we move it first to
982
  # tnode:pnode, then back to pnode:snode
983
  for si, di, pn, sn in [(src_instance.name, dest_instance.name,
984
                          tnode.primary, pnode.primary),
985
                         (dest_instance.name, src_instance.name,
986
                          pnode.primary, snode.primary)]:
987
    cmd = [
988
      "../tools/move-instance",
989
      "--verbose",
990
      "--src-ca-file=%s" % _rapi_ca.name,
991
      "--src-username=%s" % _rapi_username,
992
      "--src-password-file=%s" % rapi_pw_file.name,
993
      "--dest-instance-name=%s" % di,
994
      "--dest-primary-node=%s" % pn,
995
      "--dest-secondary-node=%s" % sn,
996
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
997
      master.primary,
998
      master.primary,
999
      si,
1000
      ]
1001

    
1002
    qa_utils.RunInstanceCheck(di, False)
1003
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
1004
    qa_utils.RunInstanceCheck(si, False)
1005
    qa_utils.RunInstanceCheck(di, True)