Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 25e9c2ce

History | View | Annotate | Download (29.3 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, put_uri, opcode_params, exceptions=None,
169
                   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 put_uri: string
176
  @param put_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 exceptions: list of string or None
181
  @param exceptions: The parameters which have not been exposed and should not
182
                     be tested at all.
183
  @type set_exceptions: list of string or None
184
  @param set_exceptions: The parameters whose setting should not be tested as a
185
                         part of this test.
186

187
  """
188

    
189
  assert get_uri.startswith("/")
190
  assert put_uri.startswith("/")
191

    
192
  if exceptions is None:
193
    exceptions = []
194
  if set_exceptions is None:
195
    set_exceptions = []
196

    
197
  print "Testing get/put symmetry of %s and %s" % (get_uri, put_uri)
198

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

    
202
  info = _rapi_client._SendRequest("GET", get_uri, None, {})
203

    
204
  missing_params = filter(lambda x: x not in info and x not in exceptions,
205
                          params_of_interest)
206
  if missing_params:
207
    raise qa_error.Error("The parameters %s which can be set through the "
208
                         "appropriate opcode are not present in the response "
209
                         "from %s" % (','.join(missing_params), get_uri))
210

    
211
  print "GET successful at %s" % get_uri
212

    
213
  # Then if we can perform a set with the same values as received
214
  put_payload = {}
215
  for param in params_of_interest:
216
    if param not in exceptions and param not in set_exceptions:
217
      put_payload[param] = info[param]
218

    
219
  _rapi_client._SendRequest("PUT", put_uri, None, put_payload)
220

    
221
  print "PUT successful at %s" % put_uri
222
# pylint: enable=W0212
223

    
224

    
225
def _VerifyReturnsJob(data):
226
  if not isinstance(data, int):
227
    AssertMatch(data, r"^\d+$")
228

    
229

    
230
def TestVersion():
231
  """Testing remote API version.
232

233
  """
234
  _DoTests([
235
    ("/version", constants.RAPI_VERSION, "GET", None),
236
    ])
237

    
238

    
239
def TestEmptyCluster():
240
  """Testing remote API on an empty cluster.
241

242
  """
243
  master = qa_config.GetMasterNode()
244
  master_full = qa_utils.ResolveNodeName(master)
245

    
246
  def _VerifyInfo(data):
247
    AssertIn("name", data)
248
    AssertIn("master", data)
249
    AssertEqual(data["master"], master_full)
250

    
251
  def _VerifyNodes(data):
252
    master_entry = {
253
      "id": master_full,
254
      "uri": "/2/nodes/%s" % master_full,
255
      }
256
    AssertIn(master_entry, data)
257

    
258
  def _VerifyNodesBulk(data):
259
    for node in data:
260
      for entry in NODE_FIELDS:
261
        AssertIn(entry, node)
262

    
263
  def _VerifyGroups(data):
264
    default_group = {
265
      "name": constants.INITIAL_NODE_GROUP_NAME,
266
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
267
      }
268
    AssertIn(default_group, data)
269

    
270
  def _VerifyGroupsBulk(data):
271
    for group in data:
272
      for field in GROUP_FIELDS:
273
        AssertIn(field, group)
274

    
275
  _DoTests([
276
    ("/", None, "GET", None),
277
    ("/2/info", _VerifyInfo, "GET", None),
278
    ("/2/tags", None, "GET", None),
279
    ("/2/nodes", _VerifyNodes, "GET", None),
280
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
281
    ("/2/groups", _VerifyGroups, "GET", None),
282
    ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
283
    ("/2/instances", [], "GET", None),
284
    ("/2/instances?bulk=1", [], "GET", None),
285
    ("/2/os", None, "GET", None),
286
    ])
287

    
288
  # Test HTTP Not Found
289
  for method in ["GET", "PUT", "POST", "DELETE"]:
290
    try:
291
      _DoTests([("/99/resource/not/here/99", None, method, None)])
292
    except rapi.client.GanetiApiError, err:
293
      AssertEqual(err.code, 404)
294
    else:
295
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
296

    
297
  # Test HTTP Not Implemented
298
  for method in ["PUT", "POST", "DELETE"]:
299
    try:
300
      _DoTests([("/version", None, method, None)])
301
    except rapi.client.GanetiApiError, err:
302
      AssertEqual(err.code, 501)
303
    else:
304
      raise qa_error.Error("Non-implemented method didn't fail")
305

    
306
  # Test GET/PUT symmetry
307
  LEGITIMATELY_MISSING = [
308
    "force",       # Standard option
309
    "add_uids",    # Modifies UID pool, is not a param itself
310
    "remove_uids", # Same as above
311
  ]
312
  NOT_EXPOSED_YET = ["hv_state", "disk_state", "modify_etc_hosts"]
313
  # The nicparams are returned under the default entry, yet accepted as they
314
  # are - this is a TODO to fix!
315
  DEFAULT_ISSUES = ["nicparams"]
316

    
317
  _DoGetPutTests("/2/info", "/2/modify", opcodes.OpClusterSetParams.OP_PARAMS,
318
                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET),
319
                 set_exceptions=DEFAULT_ISSUES)
320

    
321

    
322
def TestRapiQuery():
323
  """Testing resource queries via remote API.
324

325
  """
326
  # FIXME: the tests are failing if no LVM is enabled, investigate
327
  # if it is a bug in the QA or in the code
328
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
329
    return
330

    
331
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
332
  rnd = random.Random(7818)
333

    
334
  for what in constants.QR_VIA_RAPI:
335
    if what == constants.QR_JOB:
336
      namefield = "id"
337
    elif what == constants.QR_EXPORT:
338
      namefield = "export"
339
    else:
340
      namefield = "name"
341

    
342
    all_fields = query.ALL_FIELDS[what].keys()
343
    rnd.shuffle(all_fields)
344

    
345
    # No fields, should return everything
346
    result = _rapi_client.QueryFields(what)
347
    qresult = objects.QueryFieldsResponse.FromDict(result)
348
    AssertEqual(len(qresult.fields), len(all_fields))
349

    
350
    # One field
351
    result = _rapi_client.QueryFields(what, fields=[namefield])
352
    qresult = objects.QueryFieldsResponse.FromDict(result)
353
    AssertEqual(len(qresult.fields), 1)
354

    
355
    # Specify all fields, order must be correct
356
    result = _rapi_client.QueryFields(what, fields=all_fields)
357
    qresult = objects.QueryFieldsResponse.FromDict(result)
358
    AssertEqual(len(qresult.fields), len(all_fields))
359
    AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
360

    
361
    # Unknown field
362
    result = _rapi_client.QueryFields(what, fields=["_unknown!"])
363
    qresult = objects.QueryFieldsResponse.FromDict(result)
364
    AssertEqual(len(qresult.fields), 1)
365
    AssertEqual(qresult.fields[0].name, "_unknown!")
366
    AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
367

    
368
    # Try once more, this time without the client
369
    _DoTests([
370
      ("/2/query/%s/fields" % what, None, "GET", None),
371
      ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
372
       None, "GET", None),
373
      ])
374

    
375
    # Try missing query argument
376
    try:
377
      _DoTests([
378
        ("/2/query/%s" % what, None, "GET", None),
379
        ])
380
    except rapi.client.GanetiApiError, err:
381
      AssertEqual(err.code, 400)
382
    else:
383
      raise qa_error.Error("Request missing 'fields' parameter didn't fail")
384

    
385
    def _Check(exp_fields, data):
386
      qresult = objects.QueryResponse.FromDict(data)
387
      AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
388
      if not isinstance(qresult.data, list):
389
        raise qa_error.Error("Query did not return a list")
390

    
391
    _DoTests([
392
      # Specify fields in query
393
      ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
394
       compat.partial(_Check, all_fields), "GET", None),
395

    
396
      ("/2/query/%s?fields=%s" % (what, namefield),
397
       compat.partial(_Check, [namefield]), "GET", None),
398

    
399
      # Note the spaces
400
      ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
401
       (what, namefield, namefield, namefield),
402
       compat.partial(_Check, [namefield] * 3), "GET", None),
403

    
404
      # PUT with fields in query
405
      ("/2/query/%s?fields=%s" % (what, namefield),
406
       compat.partial(_Check, [namefield]), "PUT", {}),
407

    
408
      ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
409
         "fields": [namefield] * 4,
410
         }),
411

    
412
      ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
413
         "fields": all_fields,
414
         }),
415

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

    
420
    def _CheckFilter():
421
      _DoTests([
422
        # With filter
423
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
424
           "fields": all_fields,
425
           "filter": [qlang.OP_TRUE, namefield],
426
           }),
427
        ])
428

    
429
    if what == constants.QR_LOCK:
430
      # Locks can't be filtered
431
      try:
432
        _CheckFilter()
433
      except rapi.client.GanetiApiError, err:
434
        AssertEqual(err.code, 500)
435
      else:
436
        raise qa_error.Error("Filtering locks didn't fail")
437
    else:
438
      _CheckFilter()
439

    
440
    if what == constants.QR_NODE:
441
      # Test with filter
442
      (nodes, ) = _DoTests(
443
        [("/2/query/%s" % what,
444
          compat.partial(_Check, ["name", "master"]), "PUT",
445
          {"fields": ["name", "master"],
446
           "filter": [qlang.OP_TRUE, "master"],
447
           })])
448
      qresult = objects.QueryResponse.FromDict(nodes)
449
      AssertEqual(qresult.data, [
450
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
451
        ])
452

    
453

    
454
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
455
def TestInstance(instance):
456
  """Testing getting instance(s) info via remote API.
457

458
  """
459
  def _VerifyInstance(data):
460
    for entry in INSTANCE_FIELDS:
461
      AssertIn(entry, data)
462

    
463
  def _VerifyInstancesList(data):
464
    for instance in data:
465
      for entry in LIST_FIELDS:
466
        AssertIn(entry, instance)
467

    
468
  def _VerifyInstancesBulk(data):
469
    for instance_data in data:
470
      _VerifyInstance(instance_data)
471

    
472
  _DoTests([
473
    ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
474
    ("/2/instances", _VerifyInstancesList, "GET", None),
475
    ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
476
    ("/2/instances/%s/activate-disks" % instance.name,
477
     _VerifyReturnsJob, "PUT", None),
478
    ("/2/instances/%s/deactivate-disks" % instance.name,
479
     _VerifyReturnsJob, "PUT", None),
480
    ])
481

    
482
  # Test OpBackupPrepare
483
  (job_id, ) = _DoTests([
484
    ("/2/instances/%s/prepare-export?mode=%s" %
485
     (instance.name, constants.EXPORT_MODE_REMOTE),
486
     _VerifyReturnsJob, "PUT", None),
487
    ])
488

    
489
  result = _WaitForRapiJob(job_id)[0]
490
  AssertEqual(len(result["handshake"]), 3)
491
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
492
  AssertEqual(len(result["x509_key_name"]), 3)
493
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
494

    
495

    
496
def TestNode(node):
497
  """Testing getting node(s) info via remote API.
498

499
  """
500
  def _VerifyNode(data):
501
    for entry in NODE_FIELDS:
502
      AssertIn(entry, data)
503

    
504
  def _VerifyNodesList(data):
505
    for node in data:
506
      for entry in LIST_FIELDS:
507
        AssertIn(entry, node)
508

    
509
  def _VerifyNodesBulk(data):
510
    for node_data in data:
511
      _VerifyNode(node_data)
512

    
513
  _DoTests([
514
    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
515
    ("/2/nodes", _VerifyNodesList, "GET", None),
516
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
517
    ])
518

    
519

    
520
def _FilterTags(seq):
521
  """Removes unwanted tags from a sequence.
522

523
  """
524
  ignore_re = qa_config.get("ignore-tags-re", None)
525

    
526
  if ignore_re:
527
    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
528
  else:
529
    return seq
530

    
531

    
532
def TestTags(kind, name, tags):
533
  """Tests .../tags resources.
534

535
  """
536
  if kind == constants.TAG_CLUSTER:
537
    uri = "/2/tags"
538
  elif kind == constants.TAG_NODE:
539
    uri = "/2/nodes/%s/tags" % name
540
  elif kind == constants.TAG_INSTANCE:
541
    uri = "/2/instances/%s/tags" % name
542
  elif kind == constants.TAG_NODEGROUP:
543
    uri = "/2/groups/%s/tags" % name
544
  elif kind == constants.TAG_NETWORK:
545
    uri = "/2/networks/%s/tags" % name
546
  else:
547
    raise errors.ProgrammerError("Unknown tag kind")
548

    
549
  def _VerifyTags(data):
550
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
551

    
552
  queryargs = "&".join("tag=%s" % i for i in tags)
553

    
554
  # Add tags
555
  (job_id, ) = _DoTests([
556
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
557
    ])
558
  _WaitForRapiJob(job_id)
559

    
560
  # Retrieve tags
561
  _DoTests([
562
    (uri, _VerifyTags, "GET", None),
563
    ])
564

    
565
  # Remove tags
566
  (job_id, ) = _DoTests([
567
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
568
    ])
569
  _WaitForRapiJob(job_id)
570

    
571

    
572
def _WaitForRapiJob(job_id):
573
  """Waits for a job to finish.
574

575
  """
576
  def _VerifyJob(data):
577
    AssertEqual(data["id"], job_id)
578
    for field in JOB_FIELDS:
579
      AssertIn(field, data)
580

    
581
  _DoTests([
582
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
583
    ])
584

    
585
  return rapi.client_utils.PollJob(_rapi_client, job_id,
586
                                   cli.StdioJobPollReportCb())
587

    
588

    
589
def TestRapiNodeGroups():
590
  """Test several node group operations using RAPI.
591

592
  """
593
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
594

    
595
  # Create a group with no attributes
596
  body = {
597
    "name": group1,
598
    }
599

    
600
  (job_id, ) = _DoTests([
601
    ("/2/groups", _VerifyReturnsJob, "POST", body),
602
    ])
603

    
604
  _WaitForRapiJob(job_id)
605

    
606
  # Create a group specifying alloc_policy
607
  body = {
608
    "name": group2,
609
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
610
    }
611

    
612
  (job_id, ) = _DoTests([
613
    ("/2/groups", _VerifyReturnsJob, "POST", body),
614
    ])
615

    
616
  _WaitForRapiJob(job_id)
617

    
618
  # Modify alloc_policy
619
  body = {
620
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
621
    }
622

    
623
  (job_id, ) = _DoTests([
624
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
625
    ])
626

    
627
  _WaitForRapiJob(job_id)
628

    
629
  # Rename a group
630
  body = {
631
    "new_name": group3,
632
    }
633

    
634
  (job_id, ) = _DoTests([
635
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
636
    ])
637

    
638
  _WaitForRapiJob(job_id)
639

    
640
  # Delete groups
641
  for group in [group1, group3]:
642
    (job_id, ) = _DoTests([
643
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
644
      ])
645

    
646
    _WaitForRapiJob(job_id)
647

    
648

    
649
def TestRapiInstanceAdd(node, use_client):
650
  """Test adding a new instance via RAPI"""
651
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
652
    return
653
  instance = qa_config.AcquireInstance()
654
  instance.SetDiskTemplate(constants.DT_PLAIN)
655
  try:
656
    disks = [{"size": utils.ParseUnit(d.get("size")),
657
              "name": str(d.get("name"))}
658
             for d in qa_config.GetDiskOptions()]
659
    nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
660
    nics = [{
661
      constants.INIC_MAC: nic0_mac,
662
      }]
663

    
664
    beparams = {
665
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
666
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
667
      }
668

    
669
    if use_client:
670
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
671
                                           instance.name,
672
                                           constants.DT_PLAIN,
673
                                           disks, nics,
674
                                           os=qa_config.get("os"),
675
                                           pnode=node.primary,
676
                                           beparams=beparams)
677
    else:
678
      body = {
679
        "__version__": 1,
680
        "mode": constants.INSTANCE_CREATE,
681
        "name": instance.name,
682
        "os_type": qa_config.get("os"),
683
        "disk_template": constants.DT_PLAIN,
684
        "pnode": node.primary,
685
        "beparams": beparams,
686
        "disks": disks,
687
        "nics": nics,
688
        }
689

    
690
      (job_id, ) = _DoTests([
691
        ("/2/instances", _VerifyReturnsJob, "POST", body),
692
        ])
693

    
694
    _WaitForRapiJob(job_id)
695

    
696
    return instance
697
  except:
698
    instance.Release()
699
    raise
700

    
701

    
702
def _GenInstanceAllocationDict(node, instance):
703
  """Creates an instance allocation dict to be used with the RAPI"""
704
  instance.SetDiskTemplate(constants.DT_PLAIN)
705

    
706
  disks = [{"size": utils.ParseUnit(d.get("size")),
707
              "name": str(d.get("name"))}
708
             for d in qa_config.GetDiskOptions()]
709

    
710
  nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
711
  nics = [{
712
    constants.INIC_MAC: nic0_mac,
713
    }]
714

    
715
  beparams = {
716
    constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
717
    constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
718
    }
719

    
720
  return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
721
                                         instance.name,
722
                                         constants.DT_PLAIN,
723
                                         disks, nics,
724
                                         os=qa_config.get("os"),
725
                                         pnode=node.primary,
726
                                         beparams=beparams)
727

    
728

    
729
def TestRapiInstanceMultiAlloc(node):
730
  """Test adding two new instances via the RAPI instance-multi-alloc method"""
731
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
732
    return
733

    
734
  JOBS_KEY = "jobs"
735

    
736
  instance_one = qa_config.AcquireInstance()
737
  instance_two = qa_config.AcquireInstance()
738
  instance_list = [instance_one, instance_two]
739
  try:
740
    rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
741
                     instance_list)
742

    
743
    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
744

    
745
    results, = _WaitForRapiJob(job_id)
746

    
747
    if JOBS_KEY not in results:
748
      raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
749
                           "information about created jobs")
750

    
751
    if len(results[JOBS_KEY]) != len(instance_list):
752
      raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
753
                           "desired number of jobs!")
754

    
755
    for success, job in results[JOBS_KEY]:
756
      if success:
757
        _WaitForRapiJob(job)
758
      else:
759
        raise qa_error.Error("Failed to create instance in "
760
                             "instance-multi-alloc call")
761
  except:
762
    # Note that although released, it may be that some of the instance creations
763
    # have in fact succeeded. Handling this in a better way may be possible, but
764
    # is not necessary as the QA has already failed at this point.
765
    for instance in instance_list:
766
      instance.Release()
767
    raise
768

    
769
  return (instance_one, instance_two)
770

    
771

    
772
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
773
def TestRapiInstanceRemove(instance, use_client):
774
  """Test removing instance via RAPI"""
775
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
776
  # in RAPI or in the test
777
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
778
    return
779

    
780
  if use_client:
781
    job_id = _rapi_client.DeleteInstance(instance.name)
782
  else:
783
    (job_id, ) = _DoTests([
784
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
785
      ])
786

    
787
  _WaitForRapiJob(job_id)
788

    
789

    
790
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
791
def TestRapiInstanceMigrate(instance):
792
  """Test migrating instance via RAPI"""
793
  if not IsMigrationSupported(instance):
794
    print qa_logging.FormatInfo("Instance doesn't support migration, skipping"
795
                                " test")
796
    return
797
  # Move to secondary node
798
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
799
  qa_utils.RunInstanceCheck(instance, True)
800
  # And back to previous primary
801
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
802

    
803

    
804
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
805
def TestRapiInstanceFailover(instance):
806
  """Test failing over instance via RAPI"""
807
  if not IsFailoverSupported(instance):
808
    print qa_logging.FormatInfo("Instance doesn't support failover, skipping"
809
                                " test")
810
    return
811
  # Move to secondary node
812
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
813
  qa_utils.RunInstanceCheck(instance, True)
814
  # And back to previous primary
815
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
816

    
817

    
818
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
819
def TestRapiInstanceShutdown(instance):
820
  """Test stopping an instance via RAPI"""
821
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
822

    
823

    
824
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
825
def TestRapiInstanceStartup(instance):
826
  """Test starting an instance via RAPI"""
827
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
828

    
829

    
830
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
831
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
832
  """Test renaming instance via RAPI
833

834
  This must leave the instance with the original name (in the
835
  non-failure case).
836

837
  """
838
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
839
  qa_utils.RunInstanceCheck(rename_source, False)
840
  qa_utils.RunInstanceCheck(rename_target, False)
841
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
842
  qa_utils.RunInstanceCheck(rename_target, False)
843

    
844

    
845
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
846
def TestRapiInstanceReinstall(instance):
847
  """Test reinstalling an instance via RAPI"""
848
  if instance.disk_template == constants.DT_DISKLESS:
849
    print qa_logging.FormatInfo("Test not supported for diskless instances")
850
    return
851

    
852
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
853
  # By default, the instance is started again
854
  qa_utils.RunInstanceCheck(instance, True)
855

    
856
  # Reinstall again without starting
857
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
858
                                                 no_startup=True))
859

    
860

    
861
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
862
def TestRapiInstanceReplaceDisks(instance):
863
  """Test replacing instance disks via RAPI"""
864
  if not IsDiskReplacingSupported(instance):
865
    print qa_logging.FormatInfo("Instance doesn't support disk replacing,"
866
                                " skipping test")
867
    return
868
  fn = _rapi_client.ReplaceInstanceDisks
869
  _WaitForRapiJob(fn(instance.name,
870
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
871
  _WaitForRapiJob(fn(instance.name,
872
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
873

    
874

    
875
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
876
def TestRapiInstanceModify(instance):
877
  """Test modifying instance via RAPI"""
878
  default_hv = qa_config.GetDefaultHypervisor()
879

    
880
  def _ModifyInstance(**kwargs):
881
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
882

    
883
  _ModifyInstance(beparams={
884
    constants.BE_VCPUS: 3,
885
    })
886

    
887
  _ModifyInstance(beparams={
888
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
889
    })
890

    
891
  if default_hv == constants.HT_XEN_PVM:
892
    _ModifyInstance(hvparams={
893
      constants.HV_KERNEL_ARGS: "single",
894
      })
895
    _ModifyInstance(hvparams={
896
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
897
      })
898
  elif default_hv == constants.HT_XEN_HVM:
899
    _ModifyInstance(hvparams={
900
      constants.HV_BOOT_ORDER: "acn",
901
      })
902
    _ModifyInstance(hvparams={
903
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
904
      })
905

    
906

    
907
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
908
def TestRapiInstanceConsole(instance):
909
  """Test getting instance console information via RAPI"""
910
  result = _rapi_client.GetInstanceConsole(instance.name)
911
  console = objects.InstanceConsole.FromDict(result)
912
  AssertEqual(console.Validate(), True)
913
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
914

    
915

    
916
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
917
def TestRapiStoppedInstanceConsole(instance):
918
  """Test getting stopped instance's console information via RAPI"""
919
  try:
920
    _rapi_client.GetInstanceConsole(instance.name)
921
  except rapi.client.GanetiApiError, err:
922
    AssertEqual(err.code, 503)
923
  else:
924
    raise qa_error.Error("Getting console for stopped instance didn't"
925
                         " return HTTP 503")
926

    
927

    
928
def GetOperatingSystems():
929
  """Retrieves a list of all available operating systems.
930

931
  """
932
  return _rapi_client.GetOperatingSystems()
933

    
934

    
935
def TestInterClusterInstanceMove(src_instance, dest_instance,
936
                                 inodes, tnode):
937
  """Test tools/move-instance"""
938
  master = qa_config.GetMasterNode()
939

    
940
  rapi_pw_file = tempfile.NamedTemporaryFile()
941
  rapi_pw_file.write(_rapi_password)
942
  rapi_pw_file.flush()
943

    
944
  dest_instance.SetDiskTemplate(src_instance.disk_template)
945

    
946
  # TODO: Run some instance tests before moving back
947

    
948
  if len(inodes) > 1:
949
    # No disk template currently requires more than 1 secondary node. If this
950
    # changes, either this test must be skipped or the script must be updated.
951
    assert len(inodes) == 2
952
    snode = inodes[1]
953
  else:
954
    # instance is not redundant, but we still need to pass a node
955
    # (which will be ignored)
956
    snode = tnode
957
  pnode = inodes[0]
958
  # note: pnode:snode are the *current* nodes, so we move it first to
959
  # tnode:pnode, then back to pnode:snode
960
  for si, di, pn, sn in [(src_instance.name, dest_instance.name,
961
                          tnode.primary, pnode.primary),
962
                         (dest_instance.name, src_instance.name,
963
                          pnode.primary, snode.primary)]:
964
    cmd = [
965
      "../tools/move-instance",
966
      "--verbose",
967
      "--src-ca-file=%s" % _rapi_ca.name,
968
      "--src-username=%s" % _rapi_username,
969
      "--src-password-file=%s" % rapi_pw_file.name,
970
      "--dest-instance-name=%s" % di,
971
      "--dest-primary-node=%s" % pn,
972
      "--dest-secondary-node=%s" % sn,
973
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
974
      master.primary,
975
      master.primary,
976
      si,
977
      ]
978

    
979
    qa_utils.RunInstanceCheck(di, False)
980
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
981
    qa_utils.RunInstanceCheck(si, False)
982
    qa_utils.RunInstanceCheck(di, True)