Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 67bd83ae

History | View | Annotate | Download (28.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 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 tempfile
27
import random
28
import re
29
import itertools
30
import functools
31

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

    
43
from ganeti.http.auth import ParsePasswordFile
44
import ganeti.rapi.client        # pylint: disable=W0611
45
import ganeti.rapi.client_utils
46

    
47
import qa_config
48
import qa_utils
49
import qa_error
50

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

    
57

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

    
63

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

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

    
75
  _rapi_username = username
76
  _rapi_password = password
77

    
78
  master = qa_config.GetMasterNode()
79

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

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

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

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

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

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

    
107
  return _rapi_client
108

    
109

    
110
def LookupRapiSecret(rapi_user):
111
  """Find the RAPI secret for the given user.
112

113
  @param rapi_user: Login user
114
  @return: Login secret for the user
115

116
  """
117
  CTEXT = "{CLEARTEXT}"
118
  master = qa_config.GetMasterNode()
119
  cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)]
120
  file_content = qa_utils.GetCommandOutput(master.primary,
121
                                           utils.ShellQuoteArgs(cmd))
122
  users = ParsePasswordFile(file_content)
123
  entry = users.get(rapi_user)
124
  if not entry:
125
    raise qa_error.Error("User %s not found in RAPI users file" % rapi_user)
126
  secret = entry.password
127
  if secret.upper().startswith(CTEXT):
128
    secret = secret[len(CTEXT):]
129
  elif secret.startswith("{"):
130
    raise qa_error.Error("Unsupported password schema for RAPI user %s:"
131
                         " not a clear text password" % rapi_user)
132
  return secret
133

    
134

    
135
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
136
                   "admin_state",
137
                   "disk_template", "disk.sizes", "disk.spindles",
138
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
139
                   "beparams", "hvparams",
140
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
141

    
142
NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
143
               "mtotal", "mnode", "mfree",
144
               "pinst_cnt", "sinst_cnt", "tags")
145

    
146
GROUP_FIELDS = compat.UniqueFrozenset([
147
  "name", "uuid",
148
  "alloc_policy",
149
  "node_cnt", "node_list",
150
  ])
151

    
152
JOB_FIELDS = compat.UniqueFrozenset([
153
  "id", "ops", "status", "summary",
154
  "opstatus", "opresult", "oplog",
155
  "received_ts", "start_ts", "end_ts",
156
  ])
157

    
158
LIST_FIELDS = ("id", "uri")
159

    
160

    
161
def Enabled():
162
  """Return whether remote API tests should be run.
163

164
  """
165
  # TODO: Implement RAPI tests for virtual clusters
166
  return (qa_config.TestEnabled("rapi") and
167
          not qa_config.UseVirtualCluster())
168

    
169

    
170
def _DoTests(uris):
171
  # pylint: disable=W0212
172
  # due to _SendRequest usage
173
  results = []
174

    
175
  for uri, verify, method, body in uris:
176
    assert uri.startswith("/")
177

    
178
    print "%s %s" % (method, uri)
179
    data = _rapi_client._SendRequest(method, uri, None, body)
180

    
181
    if verify is not None:
182
      if callable(verify):
183
        verify(data)
184
      else:
185
        AssertEqual(data, verify)
186

    
187
    results.append(data)
188

    
189
  return results
190

    
191

    
192
def _VerifyReturnsJob(data):
193
  if not isinstance(data, int):
194
    AssertMatch(data, r"^\d+$")
195

    
196

    
197
def TestVersion():
198
  """Testing remote API version.
199

200
  """
201
  _DoTests([
202
    ("/version", constants.RAPI_VERSION, "GET", None),
203
    ])
204

    
205

    
206
def TestEmptyCluster():
207
  """Testing remote API on an empty cluster.
208

209
  """
210
  master = qa_config.GetMasterNode()
211
  master_full = qa_utils.ResolveNodeName(master)
212

    
213
  def _VerifyInfo(data):
214
    AssertIn("name", data)
215
    AssertIn("master", data)
216
    AssertEqual(data["master"], master_full)
217

    
218
  def _VerifyNodes(data):
219
    master_entry = {
220
      "id": master_full,
221
      "uri": "/2/nodes/%s" % master_full,
222
      }
223
    AssertIn(master_entry, data)
224

    
225
  def _VerifyNodesBulk(data):
226
    for node in data:
227
      for entry in NODE_FIELDS:
228
        AssertIn(entry, node)
229

    
230
  def _VerifyGroups(data):
231
    default_group = {
232
      "name": constants.INITIAL_NODE_GROUP_NAME,
233
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
234
      }
235
    AssertIn(default_group, data)
236

    
237
  def _VerifyGroupsBulk(data):
238
    for group in data:
239
      for field in GROUP_FIELDS:
240
        AssertIn(field, group)
241

    
242
  _DoTests([
243
    ("/", None, "GET", None),
244
    ("/2/info", _VerifyInfo, "GET", None),
245
    ("/2/tags", None, "GET", None),
246
    ("/2/nodes", _VerifyNodes, "GET", None),
247
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
248
    ("/2/groups", _VerifyGroups, "GET", None),
249
    ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
250
    ("/2/instances", [], "GET", None),
251
    ("/2/instances?bulk=1", [], "GET", None),
252
    ("/2/os", None, "GET", None),
253
    ])
254

    
255
  # Test HTTP Not Found
256
  for method in ["GET", "PUT", "POST", "DELETE"]:
257
    try:
258
      _DoTests([("/99/resource/not/here/99", None, method, None)])
259
    except rapi.client.GanetiApiError, err:
260
      AssertEqual(err.code, 404)
261
    else:
262
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
263

    
264
  # Test HTTP Not Implemented
265
  for method in ["PUT", "POST", "DELETE"]:
266
    try:
267
      _DoTests([("/version", None, method, None)])
268
    except rapi.client.GanetiApiError, err:
269
      AssertEqual(err.code, 501)
270
    else:
271
      raise qa_error.Error("Non-implemented method didn't fail")
272

    
273

    
274
def TestRapiQuery():
275
  """Testing resource queries via remote API.
276

277
  """
278
  # FIXME: the tests are failing if no LVM is enabled, investigate
279
  # if it is a bug in the QA or in the code
280
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
281
    return
282

    
283
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
284
  rnd = random.Random(7818)
285

    
286
  for what in constants.QR_VIA_RAPI:
287
    if what == constants.QR_JOB:
288
      namefield = "id"
289
      trivial_filter = [qlang.OP_GE, namefield, 0]
290
    elif what == constants.QR_EXPORT:
291
      namefield = "export"
292
      trivial_filter = [qlang.OP_REGEXP, ".*", namefield]
293
    else:
294
      namefield = "name"
295
      trivial_filter = [qlang.OP_REGEXP, ".*", namefield]
296

    
297
    all_fields = query.ALL_FIELDS[what].keys()
298
    rnd.shuffle(all_fields)
299

    
300
    # No fields, should return everything
301
    result = _rapi_client.QueryFields(what)
302
    qresult = objects.QueryFieldsResponse.FromDict(result)
303
    AssertEqual(len(qresult.fields), len(all_fields))
304

    
305
    # One field
306
    result = _rapi_client.QueryFields(what, fields=[namefield])
307
    qresult = objects.QueryFieldsResponse.FromDict(result)
308
    AssertEqual(len(qresult.fields), 1)
309

    
310
    # Specify all fields, order must be correct
311
    result = _rapi_client.QueryFields(what, fields=all_fields)
312
    qresult = objects.QueryFieldsResponse.FromDict(result)
313
    AssertEqual(len(qresult.fields), len(all_fields))
314
    AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
315

    
316
    # Unknown field
317
    result = _rapi_client.QueryFields(what, fields=["_unknown!"])
318
    qresult = objects.QueryFieldsResponse.FromDict(result)
319
    AssertEqual(len(qresult.fields), 1)
320
    AssertEqual(qresult.fields[0].name, "_unknown!")
321
    AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
322

    
323
    # Try once more, this time without the client
324
    _DoTests([
325
      ("/2/query/%s/fields" % what, None, "GET", None),
326
      ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
327
       None, "GET", None),
328
      ])
329

    
330
    # Try missing query argument
331
    try:
332
      _DoTests([
333
        ("/2/query/%s" % what, None, "GET", None),
334
        ])
335
    except rapi.client.GanetiApiError, err:
336
      AssertEqual(err.code, 400)
337
    else:
338
      raise qa_error.Error("Request missing 'fields' parameter didn't fail")
339

    
340
    def _Check(exp_fields, data):
341
      qresult = objects.QueryResponse.FromDict(data)
342
      AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
343
      if not isinstance(qresult.data, list):
344
        raise qa_error.Error("Query did not return a list")
345

    
346
    _DoTests([
347
      # Specify fields in query
348
      ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
349
       compat.partial(_Check, all_fields), "GET", None),
350

    
351
      ("/2/query/%s?fields=%s" % (what, namefield),
352
       compat.partial(_Check, [namefield]), "GET", None),
353

    
354
      # Note the spaces
355
      ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
356
       (what, namefield, namefield, namefield),
357
       compat.partial(_Check, [namefield] * 3), "GET", None)])
358

    
359
    if what in constants.QR_VIA_RAPI_PUT:
360
      _DoTests([
361
        # PUT with fields in query
362
        ("/2/query/%s?fields=%s" % (what, namefield),
363
         compat.partial(_Check, [namefield]), "PUT", {}),
364

    
365
        ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
366
           "fields": [namefield] * 4,
367
           }),
368

    
369
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
370
           "fields": all_fields,
371
           }),
372

    
373
        ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
374
           "fields": [namefield] * 4
375
         })])
376

    
377
    def _CheckFilter():
378
      _DoTests([
379
        # With filter
380
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
381
           "fields": all_fields,
382
           "filter": trivial_filter
383
           }),
384
        ])
385

    
386
    if what == constants.QR_LOCK:
387
      # Locks can't be filtered
388
      try:
389
        _CheckFilter()
390
      except rapi.client.GanetiApiError, err:
391
        AssertEqual(err.code, 500)
392
      else:
393
        raise qa_error.Error("Filtering locks didn't fail")
394
    else:
395
      if what in constants.QR_VIA_RAPI_PUT:
396
        _CheckFilter()
397

    
398
    if what == constants.QR_NODE:
399
      # Test with filter
400
      (nodes, ) = _DoTests(
401
        [("/2/query/%s" % what,
402
          compat.partial(_Check, ["name", "master"]), "PUT",
403
          {"fields": ["name", "master"],
404
           "filter": [qlang.OP_TRUE, "master"],
405
           })])
406
      qresult = objects.QueryResponse.FromDict(nodes)
407
      AssertEqual(qresult.data, [
408
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
409
        ])
410

    
411

    
412
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
413
def TestInstance(instance):
414
  """Testing getting instance(s) info via remote API.
415

416
  """
417
  def _VerifyInstance(data):
418
    for entry in INSTANCE_FIELDS:
419
      AssertIn(entry, data)
420

    
421
  def _VerifyInstancesList(data):
422
    for instance in data:
423
      for entry in LIST_FIELDS:
424
        AssertIn(entry, instance)
425

    
426
  def _VerifyInstancesBulk(data):
427
    for instance_data in data:
428
      _VerifyInstance(instance_data)
429

    
430
  _DoTests([
431
    ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
432
    ("/2/instances", _VerifyInstancesList, "GET", None),
433
    ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
434
    ("/2/instances/%s/activate-disks" % instance.name,
435
     _VerifyReturnsJob, "PUT", None),
436
    ("/2/instances/%s/deactivate-disks" % instance.name,
437
     _VerifyReturnsJob, "PUT", None),
438
    ])
439

    
440
  # Test OpBackupPrepare
441
  (job_id, ) = _DoTests([
442
    ("/2/instances/%s/prepare-export?mode=%s" %
443
     (instance.name, constants.EXPORT_MODE_REMOTE),
444
     _VerifyReturnsJob, "PUT", None),
445
    ])
446

    
447
  result = _WaitForRapiJob(job_id)[0]
448
  AssertEqual(len(result["handshake"]), 3)
449
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
450
  AssertEqual(len(result["x509_key_name"]), 3)
451
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
452

    
453

    
454
def TestNode(node):
455
  """Testing getting node(s) info via remote API.
456

457
  """
458
  def _VerifyNode(data):
459
    for entry in NODE_FIELDS:
460
      AssertIn(entry, data)
461

    
462
  def _VerifyNodesList(data):
463
    for node in data:
464
      for entry in LIST_FIELDS:
465
        AssertIn(entry, node)
466

    
467
  def _VerifyNodesBulk(data):
468
    for node_data in data:
469
      _VerifyNode(node_data)
470

    
471
  _DoTests([
472
    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
473
    ("/2/nodes", _VerifyNodesList, "GET", None),
474
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
475
    ])
476

    
477

    
478
def _FilterTags(seq):
479
  """Removes unwanted tags from a sequence.
480

481
  """
482
  ignore_re = qa_config.get("ignore-tags-re", None)
483

    
484
  if ignore_re:
485
    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
486
  else:
487
    return seq
488

    
489

    
490
def TestTags(kind, name, tags):
491
  """Tests .../tags resources.
492

493
  """
494
  if kind == constants.TAG_CLUSTER:
495
    uri = "/2/tags"
496
  elif kind == constants.TAG_NODE:
497
    uri = "/2/nodes/%s/tags" % name
498
  elif kind == constants.TAG_INSTANCE:
499
    uri = "/2/instances/%s/tags" % name
500
  elif kind == constants.TAG_NODEGROUP:
501
    uri = "/2/groups/%s/tags" % name
502
  elif kind == constants.TAG_NETWORK:
503
    uri = "/2/networks/%s/tags" % name
504
  else:
505
    raise errors.ProgrammerError("Unknown tag kind")
506

    
507
  def _VerifyTags(data):
508
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
509

    
510
  queryargs = "&".join("tag=%s" % i for i in tags)
511

    
512
  # Add tags
513
  (job_id, ) = _DoTests([
514
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
515
    ])
516
  _WaitForRapiJob(job_id)
517

    
518
  # Retrieve tags
519
  _DoTests([
520
    (uri, _VerifyTags, "GET", None),
521
    ])
522

    
523
  # Remove tags
524
  (job_id, ) = _DoTests([
525
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
526
    ])
527
  _WaitForRapiJob(job_id)
528

    
529

    
530
def _WaitForRapiJob(job_id):
531
  """Waits for a job to finish.
532

533
  """
534
  def _VerifyJob(data):
535
    AssertEqual(data["id"], job_id)
536
    for field in JOB_FIELDS:
537
      AssertIn(field, data)
538

    
539
  _DoTests([
540
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
541
    ])
542

    
543
  return rapi.client_utils.PollJob(_rapi_client, job_id,
544
                                   cli.StdioJobPollReportCb())
545

    
546

    
547
def TestRapiNodeGroups():
548
  """Test several node group operations using RAPI.
549

550
  """
551
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
552

    
553
  # Create a group with no attributes
554
  body = {
555
    "name": group1,
556
    }
557

    
558
  (job_id, ) = _DoTests([
559
    ("/2/groups", _VerifyReturnsJob, "POST", body),
560
    ])
561

    
562
  _WaitForRapiJob(job_id)
563

    
564
  # Create a group specifying alloc_policy
565
  body = {
566
    "name": group2,
567
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
568
    }
569

    
570
  (job_id, ) = _DoTests([
571
    ("/2/groups", _VerifyReturnsJob, "POST", body),
572
    ])
573

    
574
  _WaitForRapiJob(job_id)
575

    
576
  # Modify alloc_policy
577
  body = {
578
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
579
    }
580

    
581
  (job_id, ) = _DoTests([
582
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
583
    ])
584

    
585
  _WaitForRapiJob(job_id)
586

    
587
  # Rename a group
588
  body = {
589
    "new_name": group3,
590
    }
591

    
592
  (job_id, ) = _DoTests([
593
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
594
    ])
595

    
596
  _WaitForRapiJob(job_id)
597

    
598
  # Delete groups
599
  for group in [group1, group3]:
600
    (job_id, ) = _DoTests([
601
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
602
      ])
603

    
604
    _WaitForRapiJob(job_id)
605

    
606

    
607
def TestRapiInstanceAdd(node, use_client):
608
  """Test adding a new instance via RAPI"""
609
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
610
    return
611
  instance = qa_config.AcquireInstance()
612
  instance.SetDiskTemplate(constants.DT_PLAIN)
613
  try:
614
    disks = [{"size": utils.ParseUnit(d.get("size")),
615
              "name": str(d.get("name"))}
616
             for d in qa_config.GetDiskOptions()]
617
    nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
618
    nics = [{
619
      constants.INIC_MAC: nic0_mac,
620
      }]
621

    
622
    beparams = {
623
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
624
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
625
      }
626

    
627
    if use_client:
628
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
629
                                           instance.name,
630
                                           constants.DT_PLAIN,
631
                                           disks, nics,
632
                                           os=qa_config.get("os"),
633
                                           pnode=node.primary,
634
                                           beparams=beparams)
635
    else:
636
      body = {
637
        "__version__": 1,
638
        "mode": constants.INSTANCE_CREATE,
639
        "name": instance.name,
640
        "os_type": qa_config.get("os"),
641
        "disk_template": constants.DT_PLAIN,
642
        "pnode": node.primary,
643
        "beparams": beparams,
644
        "disks": disks,
645
        "nics": nics,
646
        }
647

    
648
      (job_id, ) = _DoTests([
649
        ("/2/instances", _VerifyReturnsJob, "POST", body),
650
        ])
651

    
652
    _WaitForRapiJob(job_id)
653

    
654
    return instance
655
  except:
656
    instance.Release()
657
    raise
658

    
659

    
660
def _GenInstanceAllocationDict(node, instance):
661
  """Creates an instance allocation dict to be used with the RAPI"""
662
  instance.SetDiskTemplate(constants.DT_PLAIN)
663

    
664
  disks = [{"size": utils.ParseUnit(d.get("size")),
665
              "name": str(d.get("name"))}
666
             for d in qa_config.GetDiskOptions()]
667

    
668
  nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
669
  nics = [{
670
    constants.INIC_MAC: nic0_mac,
671
    }]
672

    
673
  beparams = {
674
    constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
675
    constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
676
    }
677

    
678
  return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
679
                                         instance.name,
680
                                         constants.DT_PLAIN,
681
                                         disks, nics,
682
                                         os=qa_config.get("os"),
683
                                         pnode=node.primary,
684
                                         beparams=beparams)
685

    
686

    
687
def TestRapiInstanceMultiAlloc(node):
688
  """Test adding two new instances via the RAPI instance-multi-alloc method"""
689
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
690
    return
691

    
692
  JOBS_KEY = "jobs"
693

    
694
  instance_one = qa_config.AcquireInstance()
695
  instance_two = qa_config.AcquireInstance()
696
  instance_list = [instance_one, instance_two]
697
  try:
698
    rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
699
                     instance_list)
700

    
701
    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
702

    
703
    results, = _WaitForRapiJob(job_id)
704

    
705
    if JOBS_KEY not in results:
706
      raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
707
                           "information about created jobs")
708

    
709
    if len(results[JOBS_KEY]) != len(instance_list):
710
      raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
711
                           "desired number of jobs!")
712

    
713
    for success, job in results[JOBS_KEY]:
714
      if success:
715
        _WaitForRapiJob(job)
716
      else:
717
        raise qa_error.Error("Failed to create instance in "
718
                             "instance-multi-alloc call")
719
  except:
720
    # Note that although released, it may be that some of the instance creations
721
    # have in fact succeeded. Handling this in a better way may be possible, but
722
    # is not necessary as the QA has already failed at this point.
723
    for instance in instance_list:
724
      instance.Release()
725
    raise
726

    
727
  return (instance_one, instance_two)
728

    
729

    
730
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
731
def TestRapiInstanceRemove(instance, use_client):
732
  """Test removing instance via RAPI"""
733
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
734
  # in RAPI or in the test
735
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
736
    return
737

    
738
  if use_client:
739
    job_id = _rapi_client.DeleteInstance(instance.name)
740
  else:
741
    (job_id, ) = _DoTests([
742
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
743
      ])
744

    
745
  _WaitForRapiJob(job_id)
746

    
747

    
748
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
749
def TestRapiInstanceMigrate(instance):
750
  """Test migrating instance via RAPI"""
751
  if not IsMigrationSupported(instance):
752
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
753
                              " test")
754
    return
755
  # Move to secondary node
756
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
757
  qa_utils.RunInstanceCheck(instance, True)
758
  # And back to previous primary
759
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
760

    
761

    
762
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
763
def TestRapiInstanceFailover(instance):
764
  """Test failing over instance via RAPI"""
765
  if not IsFailoverSupported(instance):
766
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
767
                              " test")
768
    return
769
  # Move to secondary node
770
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
771
  qa_utils.RunInstanceCheck(instance, True)
772
  # And back to previous primary
773
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
774

    
775

    
776
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
777
def TestRapiInstanceShutdown(instance):
778
  """Test stopping an instance via RAPI"""
779
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
780

    
781

    
782
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
783
def TestRapiInstanceStartup(instance):
784
  """Test starting an instance via RAPI"""
785
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
786

    
787

    
788
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
789
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
790
  """Test renaming instance via RAPI
791

792
  This must leave the instance with the original name (in the
793
  non-failure case).
794

795
  """
796
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
797
  qa_utils.RunInstanceCheck(rename_source, False)
798
  qa_utils.RunInstanceCheck(rename_target, False)
799
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
800
  qa_utils.RunInstanceCheck(rename_target, False)
801

    
802

    
803
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
804
def TestRapiInstanceReinstall(instance):
805
  """Test reinstalling an instance via RAPI"""
806
  if instance.disk_template == constants.DT_DISKLESS:
807
    print qa_utils.FormatInfo("Test not supported for diskless instances")
808
    return
809

    
810
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
811
  # By default, the instance is started again
812
  qa_utils.RunInstanceCheck(instance, True)
813

    
814
  # Reinstall again without starting
815
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
816
                                                 no_startup=True))
817

    
818

    
819
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
820
def TestRapiInstanceReplaceDisks(instance):
821
  """Test replacing instance disks via RAPI"""
822
  if not IsDiskReplacingSupported(instance):
823
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
824
                              " skipping test")
825
    return
826
  fn = _rapi_client.ReplaceInstanceDisks
827
  _WaitForRapiJob(fn(instance.name,
828
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
829
  _WaitForRapiJob(fn(instance.name,
830
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
831

    
832

    
833
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
834
def TestRapiInstanceModify(instance):
835
  """Test modifying instance via RAPI"""
836
  default_hv = qa_config.GetDefaultHypervisor()
837

    
838
  def _ModifyInstance(**kwargs):
839
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
840

    
841
  _ModifyInstance(beparams={
842
    constants.BE_VCPUS: 3,
843
    })
844

    
845
  _ModifyInstance(beparams={
846
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
847
    })
848

    
849
  if default_hv == constants.HT_XEN_PVM:
850
    _ModifyInstance(hvparams={
851
      constants.HV_KERNEL_ARGS: "single",
852
      })
853
    _ModifyInstance(hvparams={
854
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
855
      })
856
  elif default_hv == constants.HT_XEN_HVM:
857
    _ModifyInstance(hvparams={
858
      constants.HV_BOOT_ORDER: "acn",
859
      })
860
    _ModifyInstance(hvparams={
861
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
862
      })
863

    
864

    
865
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
866
def TestRapiInstanceConsole(instance):
867
  """Test getting instance console information via RAPI"""
868
  result = _rapi_client.GetInstanceConsole(instance.name)
869
  console = objects.InstanceConsole.FromDict(result)
870
  AssertEqual(console.Validate(), True)
871
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
872

    
873

    
874
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
875
def TestRapiStoppedInstanceConsole(instance):
876
  """Test getting stopped instance's console information via RAPI"""
877
  try:
878
    _rapi_client.GetInstanceConsole(instance.name)
879
  except rapi.client.GanetiApiError, err:
880
    AssertEqual(err.code, 503)
881
  else:
882
    raise qa_error.Error("Getting console for stopped instance didn't"
883
                         " return HTTP 503")
884

    
885

    
886
def GetOperatingSystems():
887
  """Retrieves a list of all available operating systems.
888

889
  """
890
  return _rapi_client.GetOperatingSystems()
891

    
892

    
893
def TestInterClusterInstanceMove(src_instance, dest_instance,
894
                                 inodes, tnode, perform_checks=True):
895
  """Test tools/move-instance"""
896
  master = qa_config.GetMasterNode()
897

    
898
  rapi_pw_file = tempfile.NamedTemporaryFile()
899
  rapi_pw_file.write(_rapi_password)
900
  rapi_pw_file.flush()
901

    
902
  # Needed only if checks are to be performed
903
  if perform_checks:
904
    dest_instance.SetDiskTemplate(src_instance.disk_template)
905

    
906
  # TODO: Run some instance tests before moving back
907

    
908
  if len(inodes) > 1:
909
    # No disk template currently requires more than 1 secondary node. If this
910
    # changes, either this test must be skipped or the script must be updated.
911
    assert len(inodes) == 2
912
    snode = inodes[1]
913
  else:
914
    # instance is not redundant, but we still need to pass a node
915
    # (which will be ignored)
916
    snode = tnode
917
  pnode = inodes[0]
918
  # note: pnode:snode are the *current* nodes, so we move it first to
919
  # tnode:pnode, then back to pnode:snode
920
  for current_src_inst, current_dest_inst, target_pnode, target_snode in \
921
    [(src_instance.name, dest_instance.name, tnode.primary, pnode.primary),
922
     (dest_instance.name, src_instance.name, pnode.primary, snode.primary)]:
923
    cmd = [
924
      "../tools/move-instance",
925
      "--verbose",
926
      "--src-ca-file=%s" % _rapi_ca.name,
927
      "--src-username=%s" % _rapi_username,
928
      "--src-password-file=%s" % rapi_pw_file.name,
929
      "--dest-instance-name=%s" % current_dest_inst,
930
      "--dest-primary-node=%s" % target_pnode,
931
      "--dest-secondary-node=%s" % target_snode,
932
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
933
      master.primary,
934
      master.primary,
935
      current_src_inst,
936
      ]
937

    
938
    # Some uses of this test might require that RAPI-only commands are used,
939
    # and the checks are command-line based.
940

    
941
    if perform_checks:
942
      qa_utils.RunInstanceCheck(current_dest_inst, False)
943

    
944
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
945

    
946
    if perform_checks:
947
      qa_utils.RunInstanceCheck(current_src_inst, False)
948
      qa_utils.RunInstanceCheck(current_dest_inst, True)