Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ ccded668

History | View | Annotate | Download (25.5 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

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

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

    
46
import qa_config
47
import qa_utils
48
import qa_error
49

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

    
56

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

    
62

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

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

    
74
  _rapi_username = username
75
  _rapi_password = password
76

    
77
  master = qa_config.GetMasterNode()
78

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

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

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

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

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

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

    
106
  return _rapi_client
107

    
108

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

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

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

    
133

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

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

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

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

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

    
159

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

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

    
168

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

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

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

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

    
186
    results.append(data)
187

    
188
  return results
189

    
190

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

    
195

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

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

    
204

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

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

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

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

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

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

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

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

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

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

    
272

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
410

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

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

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

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

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

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

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

    
452

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

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

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

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

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

    
476

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

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

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

    
488

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

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

    
504
  def _VerifyTags(data):
505
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
506

    
507
  queryargs = "&".join("tag=%s" % i for i in tags)
508

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

    
515
  # Retrieve tags
516
  _DoTests([
517
    (uri, _VerifyTags, "GET", None),
518
    ])
519

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

    
526

    
527
def _WaitForRapiJob(job_id):
528
  """Waits for a job to finish.
529

530
  """
531
  def _VerifyJob(data):
532
    AssertEqual(data["id"], job_id)
533
    for field in JOB_FIELDS:
534
      AssertIn(field, data)
535

    
536
  _DoTests([
537
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
538
    ])
539

    
540
  return rapi.client_utils.PollJob(_rapi_client, job_id,
541
                                   cli.StdioJobPollReportCb())
542

    
543

    
544
def TestRapiNodeGroups():
545
  """Test several node group operations using RAPI.
546

547
  """
548
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
549

    
550
  # Create a group with no attributes
551
  body = {
552
    "name": group1,
553
    }
554

    
555
  (job_id, ) = _DoTests([
556
    ("/2/groups", _VerifyReturnsJob, "POST", body),
557
    ])
558

    
559
  _WaitForRapiJob(job_id)
560

    
561
  # Create a group specifying alloc_policy
562
  body = {
563
    "name": group2,
564
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
565
    }
566

    
567
  (job_id, ) = _DoTests([
568
    ("/2/groups", _VerifyReturnsJob, "POST", body),
569
    ])
570

    
571
  _WaitForRapiJob(job_id)
572

    
573
  # Modify alloc_policy
574
  body = {
575
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
576
    }
577

    
578
  (job_id, ) = _DoTests([
579
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
580
    ])
581

    
582
  _WaitForRapiJob(job_id)
583

    
584
  # Rename a group
585
  body = {
586
    "new_name": group3,
587
    }
588

    
589
  (job_id, ) = _DoTests([
590
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
591
    ])
592

    
593
  _WaitForRapiJob(job_id)
594

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

    
601
    _WaitForRapiJob(job_id)
602

    
603

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

    
619
    beparams = {
620
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
621
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
622
      }
623

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

    
645
      (job_id, ) = _DoTests([
646
        ("/2/instances", _VerifyReturnsJob, "POST", body),
647
        ])
648

    
649
    _WaitForRapiJob(job_id)
650

    
651
    return instance
652
  except:
653
    instance.Release()
654
    raise
655

    
656

    
657
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
658
def TestRapiInstanceRemove(instance, use_client):
659
  """Test removing instance via RAPI"""
660
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
661
  # in RAPI or in the test
662
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
663
    return
664

    
665
  if use_client:
666
    job_id = _rapi_client.DeleteInstance(instance.name)
667
  else:
668
    (job_id, ) = _DoTests([
669
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
670
      ])
671

    
672
  _WaitForRapiJob(job_id)
673

    
674

    
675
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
676
def TestRapiInstanceMigrate(instance):
677
  """Test migrating instance via RAPI"""
678
  if not IsMigrationSupported(instance):
679
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
680
                              " test")
681
    return
682
  # Move to secondary node
683
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
684
  qa_utils.RunInstanceCheck(instance, True)
685
  # And back to previous primary
686
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
687

    
688

    
689
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
690
def TestRapiInstanceFailover(instance):
691
  """Test failing over instance via RAPI"""
692
  if not IsFailoverSupported(instance):
693
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
694
                              " test")
695
    return
696
  # Move to secondary node
697
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
698
  qa_utils.RunInstanceCheck(instance, True)
699
  # And back to previous primary
700
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
701

    
702

    
703
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
704
def TestRapiInstanceShutdown(instance):
705
  """Test stopping an instance via RAPI"""
706
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
707

    
708

    
709
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
710
def TestRapiInstanceStartup(instance):
711
  """Test starting an instance via RAPI"""
712
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
713

    
714

    
715
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
716
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
717
  """Test renaming instance via RAPI
718

719
  This must leave the instance with the original name (in the
720
  non-failure case).
721

722
  """
723
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
724
  qa_utils.RunInstanceCheck(rename_source, False)
725
  qa_utils.RunInstanceCheck(rename_target, False)
726
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
727
  qa_utils.RunInstanceCheck(rename_target, False)
728

    
729

    
730
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
731
def TestRapiInstanceReinstall(instance):
732
  """Test reinstalling an instance via RAPI"""
733
  if instance.disk_template == constants.DT_DISKLESS:
734
    print qa_utils.FormatInfo("Test not supported for diskless instances")
735
    return
736

    
737
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
738
  # By default, the instance is started again
739
  qa_utils.RunInstanceCheck(instance, True)
740

    
741
  # Reinstall again without starting
742
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
743
                                                 no_startup=True))
744

    
745

    
746
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
747
def TestRapiInstanceReplaceDisks(instance):
748
  """Test replacing instance disks via RAPI"""
749
  if not IsDiskReplacingSupported(instance):
750
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
751
                              " skipping test")
752
    return
753
  fn = _rapi_client.ReplaceInstanceDisks
754
  _WaitForRapiJob(fn(instance.name,
755
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
756
  _WaitForRapiJob(fn(instance.name,
757
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
758

    
759

    
760
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
761
def TestRapiInstanceModify(instance):
762
  """Test modifying instance via RAPI"""
763
  default_hv = qa_config.GetDefaultHypervisor()
764

    
765
  def _ModifyInstance(**kwargs):
766
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
767

    
768
  _ModifyInstance(beparams={
769
    constants.BE_VCPUS: 3,
770
    })
771

    
772
  _ModifyInstance(beparams={
773
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
774
    })
775

    
776
  if default_hv == constants.HT_XEN_PVM:
777
    _ModifyInstance(hvparams={
778
      constants.HV_KERNEL_ARGS: "single",
779
      })
780
    _ModifyInstance(hvparams={
781
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
782
      })
783
  elif default_hv == constants.HT_XEN_HVM:
784
    _ModifyInstance(hvparams={
785
      constants.HV_BOOT_ORDER: "acn",
786
      })
787
    _ModifyInstance(hvparams={
788
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
789
      })
790

    
791

    
792
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
793
def TestRapiInstanceConsole(instance):
794
  """Test getting instance console information via RAPI"""
795
  result = _rapi_client.GetInstanceConsole(instance.name)
796
  console = objects.InstanceConsole.FromDict(result)
797
  AssertEqual(console.Validate(), True)
798
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
799

    
800

    
801
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
802
def TestRapiStoppedInstanceConsole(instance):
803
  """Test getting stopped instance's console information via RAPI"""
804
  try:
805
    _rapi_client.GetInstanceConsole(instance.name)
806
  except rapi.client.GanetiApiError, err:
807
    AssertEqual(err.code, 503)
808
  else:
809
    raise qa_error.Error("Getting console for stopped instance didn't"
810
                         " return HTTP 503")
811

    
812

    
813
def GetOperatingSystems():
814
  """Retrieves a list of all available operating systems.
815

816
  """
817
  return _rapi_client.GetOperatingSystems()
818

    
819

    
820
def TestInterClusterInstanceMove(src_instance, dest_instance,
821
                                 inodes, tnode, perform_checks=True):
822
  """Test tools/move-instance"""
823
  master = qa_config.GetMasterNode()
824

    
825
  rapi_pw_file = tempfile.NamedTemporaryFile()
826
  rapi_pw_file.write(_rapi_password)
827
  rapi_pw_file.flush()
828

    
829
  # Needed only if checks are to be performed
830
  if perform_checks:
831
    dest_instance.SetDiskTemplate(src_instance.disk_template)
832

    
833
  # TODO: Run some instance tests before moving back
834

    
835
  if len(inodes) > 1:
836
    # No disk template currently requires more than 1 secondary node. If this
837
    # changes, either this test must be skipped or the script must be updated.
838
    assert len(inodes) == 2
839
    snode = inodes[1]
840
  else:
841
    # instance is not redundant, but we still need to pass a node
842
    # (which will be ignored)
843
    snode = tnode
844
  pnode = inodes[0]
845
  # note: pnode:snode are the *current* nodes, so we move it first to
846
  # tnode:pnode, then back to pnode:snode
847
  for current_src_inst, current_dest_inst, target_pnode, target_snode in \
848
    [(src_instance.name, dest_instance.name, tnode.primary, pnode.primary),
849
     (dest_instance.name, src_instance.name, pnode.primary, snode.primary)]:
850
    cmd = [
851
      "../tools/move-instance",
852
      "--verbose",
853
      "--src-ca-file=%s" % _rapi_ca.name,
854
      "--src-username=%s" % _rapi_username,
855
      "--src-password-file=%s" % rapi_pw_file.name,
856
      "--dest-instance-name=%s" % current_dest_inst,
857
      "--dest-primary-node=%s" % target_pnode,
858
      "--dest-secondary-node=%s" % target_snode,
859
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
860
      master.primary,
861
      master.primary,
862
      current_src_inst,
863
      ]
864

    
865
    # Some uses of this test might require that RAPI-only commands are used,
866
    # and the checks are command-line based.
867

    
868
    if perform_checks:
869
      qa_utils.RunInstanceCheck(current_dest_inst, False)
870

    
871
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
872

    
873
    if perform_checks:
874
      qa_utils.RunInstanceCheck(current_src_inst, False)
875
      qa_utils.RunInstanceCheck(current_dest_inst, True)