Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 4c4bf2fb

History | View | Annotate | Download (29.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, modify_method="PUT",
169
                   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 modify_method: string
181
  @param modify_method: The method to be used in the modification.
182
  @type exceptions: list of string or None
183
  @param exceptions: The parameters which have not been exposed and should not
184
                     be tested at all.
185
  @type set_exceptions: list of string or None
186
  @param set_exceptions: The parameters whose setting should not be tested as a
187
                         part of this test.
188

189
  """
190

    
191
  assert get_uri.startswith("/")
192
  assert modify_uri.startswith("/")
193

    
194
  if exceptions is None:
195
    exceptions = []
196
  if set_exceptions is None:
197
    set_exceptions = []
198

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

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

    
204
  info = _rapi_client._SendRequest("GET", get_uri, None, {})
205

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

    
213
  print "GET successful at %s" % get_uri
214

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

    
221
  _rapi_client._SendRequest(modify_method, modify_uri, None, put_payload)
222

    
223
  print "%s successful at %s" % (modify_method, modify_uri)
224
# pylint: enable=W0212
225

    
226

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

    
231

    
232
def TestVersion():
233
  """Testing remote API version.
234

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

    
240

    
241
def TestEmptyCluster():
242
  """Testing remote API on an empty cluster.
243

244
  """
245
  master = qa_config.GetMasterNode()
246
  master_full = qa_utils.ResolveNodeName(master)
247

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

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

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

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

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

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

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

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

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

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

    
323

    
324
def TestRapiQuery():
325
  """Testing resource queries via remote API.
326

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

    
333
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
334
  rnd = random.Random(7818)
335

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

    
344
    all_fields = query.ALL_FIELDS[what].keys()
345
    rnd.shuffle(all_fields)
346

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
455

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

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

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

    
470
  def _VerifyInstancesBulk(data):
471
    for instance_data in data:
472
      _VerifyInstance(instance_data)
473

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

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

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

    
497

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

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

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

    
511
  def _VerifyNodesBulk(data):
512
    for node_data in data:
513
      _VerifyNode(node_data)
514

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

    
521

    
522
def _FilterTags(seq):
523
  """Removes unwanted tags from a sequence.
524

525
  """
526
  ignore_re = qa_config.get("ignore-tags-re", None)
527

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

    
533

    
534
def TestTags(kind, name, tags):
535
  """Tests .../tags resources.
536

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

    
551
  def _VerifyTags(data):
552
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
553

    
554
  queryargs = "&".join("tag=%s" % i for i in tags)
555

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

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

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

    
573

    
574
def _WaitForRapiJob(job_id):
575
  """Waits for a job to finish.
576

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

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

    
587
  return rapi.client_utils.PollJob(_rapi_client, job_id,
588
                                   cli.StdioJobPollReportCb())
589

    
590

    
591
def TestRapiNodeGroups():
592
  """Test several node group operations using RAPI.
593

594
  """
595
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
596

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

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

    
606
  _WaitForRapiJob(job_id)
607

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

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

    
618
  _WaitForRapiJob(job_id)
619

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

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

    
629
  _WaitForRapiJob(job_id)
630

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

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

    
640
  _WaitForRapiJob(job_id)
641

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

    
648
    _WaitForRapiJob(job_id)
649

    
650

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

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

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

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

    
696
    _WaitForRapiJob(job_id)
697

    
698
    return instance
699
  except:
700
    instance.Release()
701
    raise
702

    
703

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

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

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

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

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

    
730

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

    
736
  JOBS_KEY = "jobs"
737

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

    
745
    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
746

    
747
    results, = _WaitForRapiJob(job_id)
748

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

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

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

    
771
  return (instance_one, instance_two)
772

    
773

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

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

    
789
  _WaitForRapiJob(job_id)
790

    
791

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

    
805

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

    
819

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

    
825

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

    
831

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

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

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

    
846

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

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

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

    
862

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

    
876

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

    
882
  def _ModifyInstance(**kwargs):
883
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
884

    
885
  _ModifyInstance(beparams={
886
    constants.BE_VCPUS: 3,
887
    })
888

    
889
  _ModifyInstance(beparams={
890
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
891
    })
892

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

    
908

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

    
917

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

    
929

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

933
  """
934
  return _rapi_client.GetOperatingSystems()
935

    
936

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

    
942
  rapi_pw_file = tempfile.NamedTemporaryFile()
943
  rapi_pw_file.write(_rapi_password)
944
  rapi_pw_file.flush()
945

    
946
  dest_instance.SetDiskTemplate(src_instance.disk_template)
947

    
948
  # TODO: Run some instance tests before moving back
949

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

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