Statistics
| Branch: | Tag: | Revision:

root / qa / qa_rapi.py @ 2ee9171a

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

    
107
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
108
                   "admin_state",
109
                   "disk_template", "disk.sizes", "disk.spindles",
110
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
111
                   "beparams", "hvparams",
112
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
113

    
114
NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
115
               "mtotal", "mnode", "mfree",
116
               "pinst_cnt", "sinst_cnt", "tags")
117

    
118
GROUP_FIELDS = compat.UniqueFrozenset([
119
  "name", "uuid",
120
  "alloc_policy",
121
  "node_cnt", "node_list",
122
  ])
123

    
124
JOB_FIELDS = compat.UniqueFrozenset([
125
  "id", "ops", "status", "summary",
126
  "opstatus", "opresult", "oplog",
127
  "received_ts", "start_ts", "end_ts",
128
  ])
129

    
130
LIST_FIELDS = ("id", "uri")
131

    
132

    
133
def Enabled():
134
  """Return whether remote API tests should be run.
135

136
  """
137
  # TODO: Implement RAPI tests for virtual clusters
138
  return (qa_config.TestEnabled("rapi") and
139
          not qa_config.UseVirtualCluster())
140

    
141

    
142
def _DoTests(uris):
143
  # pylint: disable=W0212
144
  # due to _SendRequest usage
145
  results = []
146

    
147
  for uri, verify, method, body in uris:
148
    assert uri.startswith("/")
149

    
150
    print "%s %s" % (method, uri)
151
    data = _rapi_client._SendRequest(method, uri, None, body)
152

    
153
    if verify is not None:
154
      if callable(verify):
155
        verify(data)
156
      else:
157
        AssertEqual(data, verify)
158

    
159
    results.append(data)
160

    
161
  return results
162

    
163

    
164
def _VerifyReturnsJob(data):
165
  if not isinstance(data, int):
166
    AssertMatch(data, r"^\d+$")
167

    
168

    
169
def TestVersion():
170
  """Testing remote API version.
171

172
  """
173
  _DoTests([
174
    ("/version", constants.RAPI_VERSION, "GET", None),
175
    ])
176

    
177

    
178
def TestEmptyCluster():
179
  """Testing remote API on an empty cluster.
180

181
  """
182
  master = qa_config.GetMasterNode()
183
  master_full = qa_utils.ResolveNodeName(master)
184

    
185
  def _VerifyInfo(data):
186
    AssertIn("name", data)
187
    AssertIn("master", data)
188
    AssertEqual(data["master"], master_full)
189

    
190
  def _VerifyNodes(data):
191
    master_entry = {
192
      "id": master_full,
193
      "uri": "/2/nodes/%s" % master_full,
194
      }
195
    AssertIn(master_entry, data)
196

    
197
  def _VerifyNodesBulk(data):
198
    for node in data:
199
      for entry in NODE_FIELDS:
200
        AssertIn(entry, node)
201

    
202
  def _VerifyGroups(data):
203
    default_group = {
204
      "name": constants.INITIAL_NODE_GROUP_NAME,
205
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
206
      }
207
    AssertIn(default_group, data)
208

    
209
  def _VerifyGroupsBulk(data):
210
    for group in data:
211
      for field in GROUP_FIELDS:
212
        AssertIn(field, group)
213

    
214
  _DoTests([
215
    ("/", None, "GET", None),
216
    ("/2/info", _VerifyInfo, "GET", None),
217
    ("/2/tags", None, "GET", None),
218
    ("/2/nodes", _VerifyNodes, "GET", None),
219
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
220
    ("/2/groups", _VerifyGroups, "GET", None),
221
    ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
222
    ("/2/instances", [], "GET", None),
223
    ("/2/instances?bulk=1", [], "GET", None),
224
    ("/2/os", None, "GET", None),
225
    ])
226

    
227
  # Test HTTP Not Found
228
  for method in ["GET", "PUT", "POST", "DELETE"]:
229
    try:
230
      _DoTests([("/99/resource/not/here/99", None, method, None)])
231
    except rapi.client.GanetiApiError, err:
232
      AssertEqual(err.code, 404)
233
    else:
234
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")
235

    
236
  # Test HTTP Not Implemented
237
  for method in ["PUT", "POST", "DELETE"]:
238
    try:
239
      _DoTests([("/version", None, method, None)])
240
    except rapi.client.GanetiApiError, err:
241
      AssertEqual(err.code, 501)
242
    else:
243
      raise qa_error.Error("Non-implemented method didn't fail")
244

    
245

    
246
def TestRapiQuery():
247
  """Testing resource queries via remote API.
248

249
  """
250
  # FIXME: the tests are failing if no LVM is enabled, investigate
251
  # if it is a bug in the QA or in the code
252
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
253
    return
254

    
255
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
256
  rnd = random.Random(7818)
257

    
258
  for what in constants.QR_VIA_RAPI:
259
    if what == constants.QR_JOB:
260
      namefield = "id"
261
    elif what == constants.QR_EXPORT:
262
      namefield = "export"
263
    else:
264
      namefield = "name"
265

    
266
    all_fields = query.ALL_FIELDS[what].keys()
267
    rnd.shuffle(all_fields)
268

    
269
    # No fields, should return everything
270
    result = _rapi_client.QueryFields(what)
271
    qresult = objects.QueryFieldsResponse.FromDict(result)
272
    AssertEqual(len(qresult.fields), len(all_fields))
273

    
274
    # One field
275
    result = _rapi_client.QueryFields(what, fields=[namefield])
276
    qresult = objects.QueryFieldsResponse.FromDict(result)
277
    AssertEqual(len(qresult.fields), 1)
278

    
279
    # Specify all fields, order must be correct
280
    result = _rapi_client.QueryFields(what, fields=all_fields)
281
    qresult = objects.QueryFieldsResponse.FromDict(result)
282
    AssertEqual(len(qresult.fields), len(all_fields))
283
    AssertEqual([fdef.name for fdef in qresult.fields], all_fields)
284

    
285
    # Unknown field
286
    result = _rapi_client.QueryFields(what, fields=["_unknown!"])
287
    qresult = objects.QueryFieldsResponse.FromDict(result)
288
    AssertEqual(len(qresult.fields), 1)
289
    AssertEqual(qresult.fields[0].name, "_unknown!")
290
    AssertEqual(qresult.fields[0].kind, constants.QFT_UNKNOWN)
291

    
292
    # Try once more, this time without the client
293
    _DoTests([
294
      ("/2/query/%s/fields" % what, None, "GET", None),
295
      ("/2/query/%s/fields?fields=name,name,%s" % (what, all_fields[0]),
296
       None, "GET", None),
297
      ])
298

    
299
    # Try missing query argument
300
    try:
301
      _DoTests([
302
        ("/2/query/%s" % what, None, "GET", None),
303
        ])
304
    except rapi.client.GanetiApiError, err:
305
      AssertEqual(err.code, 400)
306
    else:
307
      raise qa_error.Error("Request missing 'fields' parameter didn't fail")
308

    
309
    def _Check(exp_fields, data):
310
      qresult = objects.QueryResponse.FromDict(data)
311
      AssertEqual([fdef.name for fdef in qresult.fields], exp_fields)
312
      if not isinstance(qresult.data, list):
313
        raise qa_error.Error("Query did not return a list")
314

    
315
    _DoTests([
316
      # Specify fields in query
317
      ("/2/query/%s?fields=%s" % (what, ",".join(all_fields)),
318
       compat.partial(_Check, all_fields), "GET", None),
319

    
320
      ("/2/query/%s?fields=%s" % (what, namefield),
321
       compat.partial(_Check, [namefield]), "GET", None),
322

    
323
      # Note the spaces
324
      ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
325
       (what, namefield, namefield, namefield),
326
       compat.partial(_Check, [namefield] * 3), "GET", None),
327

    
328
      # PUT with fields in query
329
      ("/2/query/%s?fields=%s" % (what, namefield),
330
       compat.partial(_Check, [namefield]), "PUT", {}),
331

    
332
      ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
333
         "fields": [namefield] * 4,
334
         }),
335

    
336
      ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
337
         "fields": all_fields,
338
         }),
339

    
340
      ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
341
         "fields": [namefield] * 4
342
         })])
343

    
344
    def _CheckFilter():
345
      _DoTests([
346
        # With filter
347
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
348
           "fields": all_fields,
349
           "filter": [qlang.OP_TRUE, namefield],
350
           }),
351
        ])
352

    
353
    if what == constants.QR_LOCK:
354
      # Locks can't be filtered
355
      try:
356
        _CheckFilter()
357
      except rapi.client.GanetiApiError, err:
358
        AssertEqual(err.code, 500)
359
      else:
360
        raise qa_error.Error("Filtering locks didn't fail")
361
    else:
362
      _CheckFilter()
363

    
364
    if what == constants.QR_NODE:
365
      # Test with filter
366
      (nodes, ) = _DoTests(
367
        [("/2/query/%s" % what,
368
          compat.partial(_Check, ["name", "master"]), "PUT",
369
          {"fields": ["name", "master"],
370
           "filter": [qlang.OP_TRUE, "master"],
371
           })])
372
      qresult = objects.QueryResponse.FromDict(nodes)
373
      AssertEqual(qresult.data, [
374
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
375
        ])
376

    
377

    
378
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
379
def TestInstance(instance):
380
  """Testing getting instance(s) info via remote API.
381

382
  """
383
  def _VerifyInstance(data):
384
    for entry in INSTANCE_FIELDS:
385
      AssertIn(entry, data)
386

    
387
  def _VerifyInstancesList(data):
388
    for instance in data:
389
      for entry in LIST_FIELDS:
390
        AssertIn(entry, instance)
391

    
392
  def _VerifyInstancesBulk(data):
393
    for instance_data in data:
394
      _VerifyInstance(instance_data)
395

    
396
  _DoTests([
397
    ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
398
    ("/2/instances", _VerifyInstancesList, "GET", None),
399
    ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
400
    ("/2/instances/%s/activate-disks" % instance.name,
401
     _VerifyReturnsJob, "PUT", None),
402
    ("/2/instances/%s/deactivate-disks" % instance.name,
403
     _VerifyReturnsJob, "PUT", None),
404
    ])
405

    
406
  # Test OpBackupPrepare
407
  (job_id, ) = _DoTests([
408
    ("/2/instances/%s/prepare-export?mode=%s" %
409
     (instance.name, constants.EXPORT_MODE_REMOTE),
410
     _VerifyReturnsJob, "PUT", None),
411
    ])
412

    
413
  result = _WaitForRapiJob(job_id)[0]
414
  AssertEqual(len(result["handshake"]), 3)
415
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
416
  AssertEqual(len(result["x509_key_name"]), 3)
417
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
418

    
419

    
420
def TestNode(node):
421
  """Testing getting node(s) info via remote API.
422

423
  """
424
  def _VerifyNode(data):
425
    for entry in NODE_FIELDS:
426
      AssertIn(entry, data)
427

    
428
  def _VerifyNodesList(data):
429
    for node in data:
430
      for entry in LIST_FIELDS:
431
        AssertIn(entry, node)
432

    
433
  def _VerifyNodesBulk(data):
434
    for node_data in data:
435
      _VerifyNode(node_data)
436

    
437
  _DoTests([
438
    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
439
    ("/2/nodes", _VerifyNodesList, "GET", None),
440
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
441
    ])
442

    
443

    
444
def _FilterTags(seq):
445
  """Removes unwanted tags from a sequence.
446

447
  """
448
  ignore_re = qa_config.get("ignore-tags-re", None)
449

    
450
  if ignore_re:
451
    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
452
  else:
453
    return seq
454

    
455

    
456
def TestTags(kind, name, tags):
457
  """Tests .../tags resources.
458

459
  """
460
  if kind == constants.TAG_CLUSTER:
461
    uri = "/2/tags"
462
  elif kind == constants.TAG_NODE:
463
    uri = "/2/nodes/%s/tags" % name
464
  elif kind == constants.TAG_INSTANCE:
465
    uri = "/2/instances/%s/tags" % name
466
  elif kind == constants.TAG_NODEGROUP:
467
    uri = "/2/groups/%s/tags" % name
468
  elif kind == constants.TAG_NETWORK:
469
    uri = "/2/networks/%s/tags" % name
470
  else:
471
    raise errors.ProgrammerError("Unknown tag kind")
472

    
473
  def _VerifyTags(data):
474
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
475

    
476
  queryargs = "&".join("tag=%s" % i for i in tags)
477

    
478
  # Add tags
479
  (job_id, ) = _DoTests([
480
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
481
    ])
482
  _WaitForRapiJob(job_id)
483

    
484
  # Retrieve tags
485
  _DoTests([
486
    (uri, _VerifyTags, "GET", None),
487
    ])
488

    
489
  # Remove tags
490
  (job_id, ) = _DoTests([
491
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
492
    ])
493
  _WaitForRapiJob(job_id)
494

    
495

    
496
def _WaitForRapiJob(job_id):
497
  """Waits for a job to finish.
498

499
  """
500
  def _VerifyJob(data):
501
    AssertEqual(data["id"], job_id)
502
    for field in JOB_FIELDS:
503
      AssertIn(field, data)
504

    
505
  _DoTests([
506
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
507
    ])
508

    
509
  return rapi.client_utils.PollJob(_rapi_client, job_id,
510
                                   cli.StdioJobPollReportCb())
511

    
512

    
513
def TestRapiNodeGroups():
514
  """Test several node group operations using RAPI.
515

516
  """
517
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
518

    
519
  # Create a group with no attributes
520
  body = {
521
    "name": group1,
522
    }
523

    
524
  (job_id, ) = _DoTests([
525
    ("/2/groups", _VerifyReturnsJob, "POST", body),
526
    ])
527

    
528
  _WaitForRapiJob(job_id)
529

    
530
  # Create a group specifying alloc_policy
531
  body = {
532
    "name": group2,
533
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
534
    }
535

    
536
  (job_id, ) = _DoTests([
537
    ("/2/groups", _VerifyReturnsJob, "POST", body),
538
    ])
539

    
540
  _WaitForRapiJob(job_id)
541

    
542
  # Modify alloc_policy
543
  body = {
544
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
545
    }
546

    
547
  (job_id, ) = _DoTests([
548
    ("/2/groups/%s/modify" % group1, _VerifyReturnsJob, "PUT", body),
549
    ])
550

    
551
  _WaitForRapiJob(job_id)
552

    
553
  # Rename a group
554
  body = {
555
    "new_name": group3,
556
    }
557

    
558
  (job_id, ) = _DoTests([
559
    ("/2/groups/%s/rename" % group2, _VerifyReturnsJob, "PUT", body),
560
    ])
561

    
562
  _WaitForRapiJob(job_id)
563

    
564
  # Delete groups
565
  for group in [group1, group3]:
566
    (job_id, ) = _DoTests([
567
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
568
      ])
569

    
570
    _WaitForRapiJob(job_id)
571

    
572

    
573
def TestRapiInstanceAdd(node, use_client):
574
  """Test adding a new instance via RAPI"""
575
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
576
    return
577
  instance = qa_config.AcquireInstance()
578
  instance.SetDiskTemplate(constants.DT_PLAIN)
579
  try:
580
    disks = [{"size": utils.ParseUnit(d.get("size")),
581
              "name": str(d.get("name"))}
582
             for d in qa_config.GetDiskOptions()]
583
    nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
584
    nics = [{
585
      constants.INIC_MAC: nic0_mac,
586
      }]
587

    
588
    beparams = {
589
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
590
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
591
      }
592

    
593
    if use_client:
594
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
595
                                           instance.name,
596
                                           constants.DT_PLAIN,
597
                                           disks, nics,
598
                                           os=qa_config.get("os"),
599
                                           pnode=node.primary,
600
                                           beparams=beparams)
601
    else:
602
      body = {
603
        "__version__": 1,
604
        "mode": constants.INSTANCE_CREATE,
605
        "name": instance.name,
606
        "os_type": qa_config.get("os"),
607
        "disk_template": constants.DT_PLAIN,
608
        "pnode": node.primary,
609
        "beparams": beparams,
610
        "disks": disks,
611
        "nics": nics,
612
        }
613

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

    
618
    _WaitForRapiJob(job_id)
619

    
620
    return instance
621
  except:
622
    instance.Release()
623
    raise
624

    
625

    
626
def _GenInstanceAllocationDict(node, instance):
627
  """Creates an instance allocation dict to be used with the RAPI"""
628
  instance.SetDiskTemplate(constants.DT_PLAIN)
629

    
630
  disks = [{"size": utils.ParseUnit(d.get("size")),
631
              "name": str(d.get("name"))}
632
             for d in qa_config.GetDiskOptions()]
633

    
634
  nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
635
  nics = [{
636
    constants.INIC_MAC: nic0_mac,
637
    }]
638

    
639
  beparams = {
640
    constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
641
    constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
642
    }
643

    
644
  return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
645
                                         instance.name,
646
                                         constants.DT_PLAIN,
647
                                         disks, nics,
648
                                         os=qa_config.get("os"),
649
                                         pnode=node.primary,
650
                                         beparams=beparams)
651

    
652

    
653
def TestRapiInstanceMultiAlloc(node):
654
  """Test adding two new instances via the RAPI instance-multi-alloc method"""
655
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
656
    return
657

    
658
  JOBS_KEY = "jobs"
659

    
660
  instance_one = qa_config.AcquireInstance()
661
  instance_two = qa_config.AcquireInstance()
662
  instance_list = [instance_one, instance_two]
663
  try:
664
    rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
665
                     instance_list)
666

    
667
    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)
668

    
669
    results, = _WaitForRapiJob(job_id)
670

    
671
    if JOBS_KEY not in results:
672
      raise qa_error.Error("RAPI instance-multi-alloc did not deliver "
673
                           "information about created jobs")
674

    
675
    if len(results[JOBS_KEY]) != len(instance_list):
676
      raise qa_error.Error("RAPI instance-multi-alloc failed to return the "
677
                           "desired number of jobs!")
678

    
679
    for success, job in results[JOBS_KEY]:
680
      if success:
681
        _WaitForRapiJob(job)
682
      else:
683
        raise qa_error.Error("Failed to create instance in "
684
                             "instance-multi-alloc call")
685
  except:
686
    # Note that although released, it may be that some of the instance creations
687
    # have in fact succeeded. Handling this in a better way may be possible, but
688
    # is not necessary as the QA has already failed at this point.
689
    for instance in instance_list:
690
      instance.Release()
691
    raise
692

    
693
  return (instance_one, instance_two)
694

    
695

    
696
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
697
def TestRapiInstanceRemove(instance, use_client):
698
  """Test removing instance via RAPI"""
699
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
700
  # in RAPI or in the test
701
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
702
    return
703

    
704
  if use_client:
705
    job_id = _rapi_client.DeleteInstance(instance.name)
706
  else:
707
    (job_id, ) = _DoTests([
708
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
709
      ])
710

    
711
  _WaitForRapiJob(job_id)
712

    
713

    
714
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
715
def TestRapiInstanceMigrate(instance):
716
  """Test migrating instance via RAPI"""
717
  if not IsMigrationSupported(instance):
718
    print qa_utils.FormatInfo("Instance doesn't support migration, skipping"
719
                              " test")
720
    return
721
  # Move to secondary node
722
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
723
  qa_utils.RunInstanceCheck(instance, True)
724
  # And back to previous primary
725
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
726

    
727

    
728
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
729
def TestRapiInstanceFailover(instance):
730
  """Test failing over instance via RAPI"""
731
  if not IsFailoverSupported(instance):
732
    print qa_utils.FormatInfo("Instance doesn't support failover, skipping"
733
                              " test")
734
    return
735
  # Move to secondary node
736
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
737
  qa_utils.RunInstanceCheck(instance, True)
738
  # And back to previous primary
739
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
740

    
741

    
742
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
743
def TestRapiInstanceShutdown(instance):
744
  """Test stopping an instance via RAPI"""
745
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
746

    
747

    
748
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
749
def TestRapiInstanceStartup(instance):
750
  """Test starting an instance via RAPI"""
751
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
752

    
753

    
754
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
755
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
756
  """Test renaming instance via RAPI
757

758
  This must leave the instance with the original name (in the
759
  non-failure case).
760

761
  """
762
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
763
  qa_utils.RunInstanceCheck(rename_source, False)
764
  qa_utils.RunInstanceCheck(rename_target, False)
765
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
766
  qa_utils.RunInstanceCheck(rename_target, False)
767

    
768

    
769
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
770
def TestRapiInstanceReinstall(instance):
771
  """Test reinstalling an instance via RAPI"""
772
  if instance.disk_template == constants.DT_DISKLESS:
773
    print qa_utils.FormatInfo("Test not supported for diskless instances")
774
    return
775

    
776
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
777
  # By default, the instance is started again
778
  qa_utils.RunInstanceCheck(instance, True)
779

    
780
  # Reinstall again without starting
781
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
782
                                                 no_startup=True))
783

    
784

    
785
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
786
def TestRapiInstanceReplaceDisks(instance):
787
  """Test replacing instance disks via RAPI"""
788
  if not IsDiskReplacingSupported(instance):
789
    print qa_utils.FormatInfo("Instance doesn't support disk replacing,"
790
                              " skipping test")
791
    return
792
  fn = _rapi_client.ReplaceInstanceDisks
793
  _WaitForRapiJob(fn(instance.name,
794
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
795
  _WaitForRapiJob(fn(instance.name,
796
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
797

    
798

    
799
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
800
def TestRapiInstanceModify(instance):
801
  """Test modifying instance via RAPI"""
802
  default_hv = qa_config.GetDefaultHypervisor()
803

    
804
  def _ModifyInstance(**kwargs):
805
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
806

    
807
  _ModifyInstance(beparams={
808
    constants.BE_VCPUS: 3,
809
    })
810

    
811
  _ModifyInstance(beparams={
812
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
813
    })
814

    
815
  if default_hv == constants.HT_XEN_PVM:
816
    _ModifyInstance(hvparams={
817
      constants.HV_KERNEL_ARGS: "single",
818
      })
819
    _ModifyInstance(hvparams={
820
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
821
      })
822
  elif default_hv == constants.HT_XEN_HVM:
823
    _ModifyInstance(hvparams={
824
      constants.HV_BOOT_ORDER: "acn",
825
      })
826
    _ModifyInstance(hvparams={
827
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
828
      })
829

    
830

    
831
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
832
def TestRapiInstanceConsole(instance):
833
  """Test getting instance console information via RAPI"""
834
  result = _rapi_client.GetInstanceConsole(instance.name)
835
  console = objects.InstanceConsole.FromDict(result)
836
  AssertEqual(console.Validate(), True)
837
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
838

    
839

    
840
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
841
def TestRapiStoppedInstanceConsole(instance):
842
  """Test getting stopped instance's console information via RAPI"""
843
  try:
844
    _rapi_client.GetInstanceConsole(instance.name)
845
  except rapi.client.GanetiApiError, err:
846
    AssertEqual(err.code, 503)
847
  else:
848
    raise qa_error.Error("Getting console for stopped instance didn't"
849
                         " return HTTP 503")
850

    
851

    
852
def GetOperatingSystems():
853
  """Retrieves a list of all available operating systems.
854

855
  """
856
  return _rapi_client.GetOperatingSystems()
857

    
858

    
859
def TestInterClusterInstanceMove(src_instance, dest_instance,
860
                                 inodes, tnode):
861
  """Test tools/move-instance"""
862
  master = qa_config.GetMasterNode()
863

    
864
  rapi_pw_file = tempfile.NamedTemporaryFile()
865
  rapi_pw_file.write(_rapi_password)
866
  rapi_pw_file.flush()
867

    
868
  dest_instance.SetDiskTemplate(src_instance.disk_template)
869

    
870
  # TODO: Run some instance tests before moving back
871

    
872
  if len(inodes) > 1:
873
    # No disk template currently requires more than 1 secondary node. If this
874
    # changes, either this test must be skipped or the script must be updated.
875
    assert len(inodes) == 2
876
    snode = inodes[1]
877
  else:
878
    # instance is not redundant, but we still need to pass a node
879
    # (which will be ignored)
880
    snode = tnode
881
  pnode = inodes[0]
882
  # note: pnode:snode are the *current* nodes, so we move it first to
883
  # tnode:pnode, then back to pnode:snode
884
  for si, di, pn, sn in [(src_instance.name, dest_instance.name,
885
                          tnode.primary, pnode.primary),
886
                         (dest_instance.name, src_instance.name,
887
                          pnode.primary, snode.primary)]:
888
    cmd = [
889
      "../tools/move-instance",
890
      "--verbose",
891
      "--src-ca-file=%s" % _rapi_ca.name,
892
      "--src-username=%s" % _rapi_username,
893
      "--src-password-file=%s" % rapi_pw_file.name,
894
      "--dest-instance-name=%s" % di,
895
      "--dest-primary-node=%s" % pn,
896
      "--dest-secondary-node=%s" % sn,
897
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
898
      master.primary,
899
      master.primary,
900
      si,
901
      ]
902

    
903
    qa_utils.RunInstanceCheck(di, False)
904
    AssertEqual(StartLocalCommand(cmd).wait(), 0)
905
    qa_utils.RunInstanceCheck(si, False)
906
    qa_utils.RunInstanceCheck(di, True)