Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 31d3b918

History | View | Annotate | Download (25.6 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
  elif kind == constants.TAG_NETWORK:
502
    uri = "/2/networks/%s/tags" % name
503
  else:
504
    raise errors.ProgrammerError("Unknown tag kind")
505

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

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

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

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

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

    
528

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

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

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

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

    
545

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

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

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

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

    
561
  _WaitForRapiJob(job_id)
562

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

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

    
573
  _WaitForRapiJob(job_id)
574

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

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

    
584
  _WaitForRapiJob(job_id)
585

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

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

    
595
  _WaitForRapiJob(job_id)
596

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

    
603
    _WaitForRapiJob(job_id)
604

    
605

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

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

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

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

    
651
    _WaitForRapiJob(job_id)
652

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

    
658

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

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

    
674
  _WaitForRapiJob(job_id)
675

    
676

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

    
690

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

    
704

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

    
710

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

    
716

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

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

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

    
731

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

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

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

    
747

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

    
761

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

    
767
  def _ModifyInstance(**kwargs):
768
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
769

    
770
  _ModifyInstance(beparams={
771
    constants.BE_VCPUS: 3,
772
    })
773

    
774
  _ModifyInstance(beparams={
775
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
776
    })
777

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

    
793

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

    
802

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

    
814

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

818
  """
819
  return _rapi_client.GetOperatingSystems()
820

    
821

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

    
827
  rapi_pw_file = tempfile.NamedTemporaryFile()
828
  rapi_pw_file.write(_rapi_password)
829
  rapi_pw_file.flush()
830

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

    
835
  # TODO: Run some instance tests before moving back
836

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

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

    
870
    if perform_checks:
871
      qa_utils.RunInstanceCheck(current_dest_inst, False)
872

    
873
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
874

    
875
    if perform_checks:
876
      qa_utils.RunInstanceCheck(current_src_inst, False)
877
      qa_utils.RunInstanceCheck(current_dest_inst, True)