qa_rapi.py 35.9 KB
Newer Older
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
1
#
2
#
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
3

Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
4
# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
Klaus Aehlig's avatar
Klaus Aehlig committed
5
# All rights reserved.
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
6
#
Klaus Aehlig's avatar
Klaus Aehlig committed
7 8 9
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
10
#
Klaus Aehlig's avatar
Klaus Aehlig committed
11 12
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
13
#
Klaus Aehlig's avatar
Klaus Aehlig committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
29 30 31 32 33 34


"""Remote API QA tests.

"""

Niklas Hambuechen's avatar
Niklas Hambuechen committed
35
import copy
36
import functools
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
37
import itertools
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
38 39 40
import random
import re
import tempfile
Niklas Hambuechen's avatar
Niklas Hambuechen committed
41
import uuid as uuid_module
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
42

Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
43 44
from ganeti import cli
from ganeti import compat
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
45 46
from ganeti import constants
from ganeti import errors
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
47
from ganeti import locking
48
from ganeti import objects
49
from ganeti import opcodes
50
from ganeti import pathutils
51
from ganeti import qlang
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
52 53 54
from ganeti import query
from ganeti import rapi
from ganeti import utils
Michael Hanselmann's avatar
Michael Hanselmann committed
55

56
from ganeti.http.auth import ParsePasswordFile
57
import ganeti.rapi.client        # pylint: disable=W0611
Michael Hanselmann's avatar
Michael Hanselmann committed
58
import ganeti.rapi.client_utils
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
59 60 61

import qa_config
import qa_error
62
import qa_logging
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
63
import qa_utils
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
64

Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
65
from qa_instance import IsDiskReplacingSupported
66 67
from qa_instance import IsFailoverSupported
from qa_instance import IsMigrationSupported
68
from qa_job_utils import RunWithLocks
Iustin Pop's avatar
Iustin Pop committed
69
from qa_utils import (AssertEqual, AssertIn, AssertMatch, StartLocalCommand)
70
from qa_utils import InstanceCheck, INST_DOWN, INST_UP, FIRST_ARG
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
71 72


Michael Hanselmann's avatar
Michael Hanselmann committed
73 74
_rapi_ca = None
_rapi_client = None
75 76
_rapi_username = None
_rapi_password = None
77 78


Michael Hanselmann's avatar
Michael Hanselmann committed
79 80
def Setup(username, password):
  """Configures the RAPI client.
81

Michael Hanselmann's avatar
Michael Hanselmann committed
82
  """
83
  # pylint: disable=W0603
Iustin Pop's avatar
Iustin Pop committed
84
  # due to global usage
Michael Hanselmann's avatar
Michael Hanselmann committed
85 86
  global _rapi_ca
  global _rapi_client
87 88 89 90 91
  global _rapi_username
  global _rapi_password

  _rapi_username = username
  _rapi_password = password
92

Michael Hanselmann's avatar
Michael Hanselmann committed
93
  master = qa_config.GetMasterNode()
94

Michael Hanselmann's avatar
Michael Hanselmann committed
95
  # Load RAPI certificate from master node
96
  cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_CERT_FILE)]
97

Michael Hanselmann's avatar
Michael Hanselmann committed
98 99
  # Write to temporary file
  _rapi_ca = tempfile.NamedTemporaryFile()
100
  _rapi_ca.write(qa_utils.GetCommandOutput(master.primary,
Michael Hanselmann's avatar
Michael Hanselmann committed
101 102
                                           utils.ShellQuoteArgs(cmd)))
  _rapi_ca.flush()
103

Michael Hanselmann's avatar
Michael Hanselmann committed
104
  port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
105 106
  cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
                                           proxy="")
107

108 109
  if qa_config.UseVirtualCluster():
    # TODO: Implement full support for RAPI on virtual clusters
110 111
    print qa_logging.FormatWarning("RAPI tests are not yet supported on"
                                   " virtual clusters and will be disabled")
112

113 114 115 116 117 118 119 120
    assert _rapi_client is None
  else:
    _rapi_client = rapi.client.GanetiRapiClient(master.primary, port=port,
                                                username=username,
                                                password=password,
                                                curl_config_fn=cfg_curl)

    print "RAPI protocol version: %s" % _rapi_client.GetVersion()
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
121

122 123
  return _rapi_client

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
def LookupRapiSecret(rapi_user):
  """Find the RAPI secret for the given user.

  @param rapi_user: Login user
  @return: Login secret for the user

  """
  CTEXT = "{CLEARTEXT}"
  master = qa_config.GetMasterNode()
  cmd = ["cat", qa_utils.MakeNodePath(master, pathutils.RAPI_USERS_FILE)]
  file_content = qa_utils.GetCommandOutput(master.primary,
                                           utils.ShellQuoteArgs(cmd))
  users = ParsePasswordFile(file_content)
  entry = users.get(rapi_user)
  if not entry:
    raise qa_error.Error("User %s not found in RAPI users file" % rapi_user)
  secret = entry.password
  if secret.upper().startswith(CTEXT):
    secret = secret[len(CTEXT):]
  elif secret.startswith("{"):
    raise qa_error.Error("Unsupported password schema for RAPI user %s:"
                         " not a clear text password" % rapi_user)
  return secret


Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
150
INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
151
                   "admin_state",
152
                   "disk_template", "disk.sizes", "disk.spindles",
153
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
154
                   "beparams", "hvparams",
155
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
156

Bernardo Dal Seno's avatar
Bernardo Dal Seno committed
157
NODE_FIELDS = ("name", "dtotal", "dfree", "sptotal", "spfree",
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
158 159 160
               "mtotal", "mnode", "mfree",
               "pinst_cnt", "sinst_cnt", "tags")

161
GROUP_FIELDS = compat.UniqueFrozenset([
162
  "name", "uuid",
163
  "alloc_policy",
164 165 166
  "node_cnt", "node_list",
  ])

167
JOB_FIELDS = compat.UniqueFrozenset([
168 169 170 171 172
  "id", "ops", "status", "summary",
  "opstatus", "opresult", "oplog",
  "received_ts", "start_ts", "end_ts",
  ])

Niklas Hambuechen's avatar
Niklas Hambuechen committed
173 174 175 176 177 178 179 180 181
FILTER_FIELDS = compat.UniqueFrozenset([
  "watermark",
  "priority",
  "predicates",
  "action",
  "reason_trail",
  "uuid",
  ])

Iustin Pop's avatar
Iustin Pop committed
182
LIST_FIELDS = ("id", "uri")
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
183 184 185 186 187 188


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

  """
189 190 191
  # TODO: Implement RAPI tests for virtual clusters
  return (qa_config.TestEnabled("rapi") and
          not qa_config.UseVirtualCluster())
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
192 193 194


def _DoTests(uris):
195
  # pylint: disable=W0212
Iustin Pop's avatar
Iustin Pop committed
196
  # due to _SendRequest usage
197
  results = []
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
198

199
  for uri, verify, method, body in uris:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
200 201
    assert uri.startswith("/")

202
    print "%s %s" % (method, uri)
Michael Hanselmann's avatar
Michael Hanselmann committed
203
    data = _rapi_client._SendRequest(method, uri, None, body)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
204 205 206 207 208 209 210

    if verify is not None:
      if callable(verify):
        verify(data)
      else:
        AssertEqual(data, verify)

211
    results.append(data)
212 213 214 215

  return results


216 217
# pylint: disable=W0212
# Due to _SendRequest usage
218 219
def _DoGetPutTests(get_uri, modify_uri, opcode_params, rapi_only_aliases=None,
                   modify_method="PUT", exceptions=None, set_exceptions=None):
220 221 222 223 224
  """ Test if all params of an object can be retrieved, and set as well.

  @type get_uri: string
  @param get_uri: The URI from which information about the object can be
                  retrieved.
225 226
  @type modify_uri: string
  @param modify_uri: The URI which can be used to modify the object.
227 228 229
  @type opcode_params: list of tuple
  @param opcode_params: The parameters of the underlying opcode, used to
                        determine which parameters are actually present.
230 231 232
  @type rapi_only_aliases: list of string or None
  @param rapi_only_aliases: Aliases for parameters which differ from the opcode,
                            and become renamed before opcode submission.
233 234
  @type modify_method: string
  @param modify_method: The method to be used in the modification.
235 236 237 238 239 240 241 242 243 244
  @type exceptions: list of string or None
  @param exceptions: The parameters which have not been exposed and should not
                     be tested at all.
  @type set_exceptions: list of string or None
  @param set_exceptions: The parameters whose setting should not be tested as a
                         part of this test.

  """

  assert get_uri.startswith("/")
245
  assert modify_uri.startswith("/")
246 247 248 249 250 251

  if exceptions is None:
    exceptions = []
  if set_exceptions is None:
    set_exceptions = []

252
  print "Testing get/modify symmetry of %s and %s" % (get_uri, modify_uri)
253 254 255 256

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

257 258 259 260
  # The RAPI-specific aliases are to be checked as well
  if rapi_only_aliases is not None:
    params_of_interest.extend(rapi_only_aliases)

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
  info = _rapi_client._SendRequest("GET", get_uri, None, {})

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

  print "GET successful at %s" % get_uri

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

278
  _rapi_client._SendRequest(modify_method, modify_uri, None, put_payload)
279

280
  print "%s successful at %s" % (modify_method, modify_uri)
281 282 283
# pylint: enable=W0212


284
def _VerifyReturnsJob(data):
Iustin Pop's avatar
Iustin Pop committed
285 286
  if not isinstance(data, int):
    AssertMatch(data, r"^\d+$")
287

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
288 289 290 291 292 293

def TestVersion():
  """Testing remote API version.

  """
  _DoTests([
Iustin Pop's avatar
Iustin Pop committed
294
    ("/version", constants.RAPI_VERSION, "GET", None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
295 296 297 298 299 300 301
    ])


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

  """
302 303
  master = qa_config.GetMasterNode()
  master_full = qa_utils.ResolveNodeName(master)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
304 305 306 307

  def _VerifyInfo(data):
    AssertIn("name", data)
    AssertIn("master", data)
308
    AssertEqual(data["master"], master_full)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
309 310 311

  def _VerifyNodes(data):
    master_entry = {
312 313
      "id": master_full,
      "uri": "/2/nodes/%s" % master_full,
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
314 315 316 317 318 319 320 321
      }
    AssertIn(master_entry, data)

  def _VerifyNodesBulk(data):
    for node in data:
      for entry in NODE_FIELDS:
        AssertIn(entry, node)

322 323
  def _VerifyGroups(data):
    default_group = {
324 325
      "name": constants.INITIAL_NODE_GROUP_NAME,
      "uri": "/2/groups/" + constants.INITIAL_NODE_GROUP_NAME,
326 327 328 329 330 331 332 333
      }
    AssertIn(default_group, data)

  def _VerifyGroupsBulk(data):
    for group in data:
      for field in GROUP_FIELDS:
        AssertIn(field, group)

Niklas Hambuechen's avatar
Niklas Hambuechen committed
334 335 336 337 338
  def _VerifyFiltersBulk(data):
    for group in data:
      for field in FILTER_FIELDS:
        AssertIn(field, group)

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
339
  _DoTests([
Iustin Pop's avatar
Iustin Pop committed
340 341 342 343 344 345 346 347 348 349
    ("/", None, "GET", None),
    ("/2/info", _VerifyInfo, "GET", None),
    ("/2/tags", None, "GET", None),
    ("/2/nodes", _VerifyNodes, "GET", None),
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
    ("/2/groups", _VerifyGroups, "GET", None),
    ("/2/groups?bulk=1", _VerifyGroupsBulk, "GET", None),
    ("/2/instances", [], "GET", None),
    ("/2/instances?bulk=1", [], "GET", None),
    ("/2/os", None, "GET", None),
Niklas Hambuechen's avatar
Niklas Hambuechen committed
350 351
    ("/2/filters", [], "GET", None),
    ("/2/filters?bulk=1", _VerifyFiltersBulk, "GET", None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
352 353
    ])

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
  # Test HTTP Not Found
  for method in ["GET", "PUT", "POST", "DELETE"]:
    try:
      _DoTests([("/99/resource/not/here/99", None, method, None)])
    except rapi.client.GanetiApiError, err:
      AssertEqual(err.code, 404)
    else:
      raise qa_error.Error("Non-existent resource didn't return HTTP 404")

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

372 373 374 375 376
  # Test GET/PUT symmetry
  LEGITIMATELY_MISSING = [
    "force",       # Standard option
    "add_uids",    # Modifies UID pool, is not a param itself
    "remove_uids", # Same as above
377
    "osparams_private_cluster", # Should not be returned
378 379 380 381 382
  ]
  NOT_EXPOSED_YET = ["hv_state", "disk_state", "modify_etc_hosts"]
  # The nicparams are returned under the default entry, yet accepted as they
  # are - this is a TODO to fix!
  DEFAULT_ISSUES = ["nicparams"]
383 384
  # Cannot be set over RAPI due to security issues
  FORBIDDEN_PARAMS = ["compression_tools"]
385 386 387

  _DoGetPutTests("/2/info", "/2/modify", opcodes.OpClusterSetParams.OP_PARAMS,
                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET),
388
                 set_exceptions=DEFAULT_ISSUES + FORBIDDEN_PARAMS)
389

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
390

391 392 393 394
def TestRapiQuery():
  """Testing resource queries via remote API.

  """
395 396 397 398 399
  # FIXME: the tests are failing if no LVM is enabled, investigate
  # if it is a bug in the QA or in the code
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
    return

400 401 402 403
  master_name = qa_utils.ResolveNodeName(qa_config.GetMasterNode())
  rnd = random.Random(7818)

  for what in constants.QR_VIA_RAPI:
404 405 406
    namefield = {
      constants.QR_JOB: "id",
      constants.QR_EXPORT: "export",
407
      constants.QR_FILTER: "uuid",
408
    }.get(what, "name")
409

410 411 412 413 414 415 416 417 418
    all_fields = query.ALL_FIELDS[what].keys()
    rnd.shuffle(all_fields)

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

    # One field
419
    result = _rapi_client.QueryFields(what, fields=[namefield])
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
    qresult = objects.QueryFieldsResponse.FromDict(result)
    AssertEqual(len(qresult.fields), 1)

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

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

    # Try once more, this time without the client
    _DoTests([
      ("/2/query/%s/fields" % what, None, "GET", None),
439 440
      ("/2/query/%s/fields?fields=%s,%s,%s" % (what, namefield, namefield,
                                               all_fields[0]),
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
       None, "GET", None),
      ])

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

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

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

465 466
      ("/2/query/%s?fields=%s" % (what, namefield),
       compat.partial(_Check, [namefield]), "GET", None),
467 468

      # Note the spaces
469 470
      ("/2/query/%s?fields=%s,%%20%s%%09,%s%%20" %
       (what, namefield, namefield, namefield),
471
       compat.partial(_Check, [namefield] * 3), "GET", None)])
472

473 474 475 476 477
    if what in constants.QR_VIA_RAPI_PUT:
      _DoTests([
        # PUT with fields in query
        ("/2/query/%s?fields=%s" % (what, namefield),
         compat.partial(_Check, [namefield]), "PUT", {}),
478

479 480 481
        ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
           "fields": [namefield] * 4,
           }),
482

483 484 485
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
           "fields": all_fields,
           }),
486

487 488
        ("/2/query/%s" % what, compat.partial(_Check, [namefield] * 4), "PUT", {
           "fields": [namefield] * 4
489
         })])
490

491 492 493 494 495
    if what in constants.QR_VIA_RAPI_PUT:
      trivial_filter = {
        constants.QR_JOB: [qlang.OP_GE, namefield, 0],
      }.get(what, [qlang.OP_REGEXP, namefield, ".*"])

496 497 498 499
      _DoTests([
        # With filter
        ("/2/query/%s" % what, compat.partial(_Check, all_fields), "PUT", {
           "fields": all_fields,
Klaus Aehlig's avatar
Klaus Aehlig committed
500
           "filter": trivial_filter
501 502 503 504 505
           }),
        ])

    if what == constants.QR_NODE:
      # Test with filter
Iustin Pop's avatar
Iustin Pop committed
506 507 508 509 510 511
      (nodes, ) = _DoTests(
        [("/2/query/%s" % what,
          compat.partial(_Check, ["name", "master"]), "PUT",
          {"fields": ["name", "master"],
           "filter": [qlang.OP_TRUE, "master"],
           })])
512 513 514 515 516 517
      qresult = objects.QueryResponse.FromDict(nodes)
      AssertEqual(qresult.data, [
        [[constants.RS_NORMAL, master_name], [constants.RS_NORMAL, True]],
        ])


518
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
519 520 521 522 523 524 525
def TestInstance(instance):
  """Testing getting instance(s) info via remote API.

  """
  def _VerifyInstance(data):
    for entry in INSTANCE_FIELDS:
      AssertIn(entry, data)
526

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
527 528
  def _VerifyInstancesList(data):
    for instance in data:
529
      for entry in LIST_FIELDS:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
530
        AssertIn(entry, instance)
531

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
532 533 534 535 536
  def _VerifyInstancesBulk(data):
    for instance_data in data:
      _VerifyInstance(instance_data)

  _DoTests([
537
    ("/2/instances/%s" % instance.name, _VerifyInstance, "GET", None),
Iustin Pop's avatar
Iustin Pop committed
538 539
    ("/2/instances", _VerifyInstancesList, "GET", None),
    ("/2/instances?bulk=1", _VerifyInstancesBulk, "GET", None),
540
    ("/2/instances/%s/activate-disks" % instance.name,
Iustin Pop's avatar
Iustin Pop committed
541
     _VerifyReturnsJob, "PUT", None),
542
    ("/2/instances/%s/deactivate-disks" % instance.name,
Iustin Pop's avatar
Iustin Pop committed
543
     _VerifyReturnsJob, "PUT", None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
544 545
    ])

546
  # Test OpBackupPrepare
547 548
  (job_id, ) = _DoTests([
    ("/2/instances/%s/prepare-export?mode=%s" %
549
     (instance.name, constants.EXPORT_MODE_REMOTE),
550 551 552 553 554 555 556 557 558
     _VerifyReturnsJob, "PUT", None),
    ])

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
559 560 561 562 563 564 565 566

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

  """
  def _VerifyNode(data):
    for entry in NODE_FIELDS:
      AssertIn(entry, data)
567

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
568 569
  def _VerifyNodesList(data):
    for node in data:
570
      for entry in LIST_FIELDS:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
571
        AssertIn(entry, node)
572

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
573 574 575 576 577
  def _VerifyNodesBulk(data):
    for node_data in data:
      _VerifyNode(node_data)

  _DoTests([
578
    ("/2/nodes/%s" % node.primary, _VerifyNode, "GET", None),
Iustin Pop's avatar
Iustin Pop committed
579 580
    ("/2/nodes", _VerifyNodesList, "GET", None),
    ("/2/nodes?bulk=1", _VerifyNodesBulk, "GET", None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
581 582
    ])

583 584 585 586 587 588 589 590 591 592 593 594 595 596
  # Not parameters of the node, but controlling opcode behavior
  LEGITIMATELY_MISSING = ["force", "powered"]
  # Identifying the node - RAPI provides these itself
  IDENTIFIERS = ["node_name", "node_uuid"]
  # As the name states, these can be set but not retrieved yet
  NOT_EXPOSED_YET = ["hv_state", "disk_state", "auto_promote"]

  _DoGetPutTests("/2/nodes/%s" % node.primary,
                 "/2/nodes/%s/modify" % node.primary,
                 opcodes.OpNodeSetParams.OP_PARAMS,
                 modify_method="POST",
                 exceptions=(LEGITIMATELY_MISSING + NOT_EXPOSED_YET +
                             IDENTIFIERS))

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
597

598 599 600 601 602 603 604 605 606 607 608 609
def _FilterTags(seq):
  """Removes unwanted tags from a sequence.

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

  if ignore_re:
    return itertools.ifilterfalse(re.compile(ignore_re).match, seq)
  else:
    return seq


Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
610 611 612 613 614
def TestTags(kind, name, tags):
  """Tests .../tags resources.

  """
  if kind == constants.TAG_CLUSTER:
615
    uri = "/2/tags"
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
616
  elif kind == constants.TAG_NODE:
617
    uri = "/2/nodes/%s/tags" % name
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
618
  elif kind == constants.TAG_INSTANCE:
619
    uri = "/2/instances/%s/tags" % name
620 621
  elif kind == constants.TAG_NODEGROUP:
    uri = "/2/groups/%s/tags" % name
Hrvoje Ribicic's avatar
Hrvoje Ribicic committed
622 623
  elif kind == constants.TAG_NETWORK:
    uri = "/2/networks/%s/tags" % name
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
624 625 626 627
  else:
    raise errors.ProgrammerError("Unknown tag kind")

  def _VerifyTags(data):
628
    AssertEqual(sorted(tags), sorted(_FilterTags(data)))
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
629

630
  queryargs = "&".join("tag=%s" % i for i in tags)
631 632 633

  # Add tags
  (job_id, ) = _DoTests([
634
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "PUT", None),
635 636 637 638
    ])
  _WaitForRapiJob(job_id)

  # Retrieve tags
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
639
  _DoTests([
Iustin Pop's avatar
Iustin Pop committed
640
    (uri, _VerifyTags, "GET", None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
641
    ])
642

643 644
  # Remove tags
  (job_id, ) = _DoTests([
645
    ("%s?%s" % (uri, queryargs), _VerifyReturnsJob, "DELETE", None),
646 647 648
    ])
  _WaitForRapiJob(job_id)

649 650 651 652 653 654 655 656 657 658 659 660 661 662

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

  """
  def _VerifyJob(data):
    AssertEqual(data["id"], job_id)
    for field in JOB_FIELDS:
      AssertIn(field, data)

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

663 664
  return rapi.client_utils.PollJob(_rapi_client, job_id,
                                   cli.StdioJobPollReportCb())
665 666


667 668 669 670
def TestRapiNodeGroups():
  """Test several node group operations using RAPI.

  """
671
  (group1, group2, group3) = qa_utils.GetNonexistentGroups(3)
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717

  # Create a group with no attributes
  body = {
    "name": group1,
    }

  (job_id, ) = _DoTests([
    ("/2/groups", _VerifyReturnsJob, "POST", body),
    ])

  _WaitForRapiJob(job_id)

  # Create a group specifying alloc_policy
  body = {
    "name": group2,
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
    }

  (job_id, ) = _DoTests([
    ("/2/groups", _VerifyReturnsJob, "POST", body),
    ])

  _WaitForRapiJob(job_id)

  # Modify alloc_policy
  body = {
    "alloc_policy": constants.ALLOC_POLICY_UNALLOCABLE,
    }

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

  _WaitForRapiJob(job_id)

  # Rename a group
  body = {
    "new_name": group3,
    }

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

  _WaitForRapiJob(job_id)

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
  # Test for get/set symmetry

  # Identifying the node - RAPI provides these itself
  IDENTIFIERS = ["group_name"]
  # As the name states, not exposed yet
  NOT_EXPOSED_YET = ["hv_state", "disk_state"]

  # The parameters we do not want to get and set (as that sets the
  # group-specific params to the filled ones)
  FILLED_PARAMS = ["ndparams", "ipolicy", "diskparams"]

  # The aliases that we can use to perform this test with the group-specific
  # params
  CUSTOM_PARAMS = ["custom_ndparams", "custom_ipolicy", "custom_diskparams"]

  _DoGetPutTests("/2/groups/%s" % group3, "/2/groups/%s/modify" % group3,
                 opcodes.OpGroupSetParams.OP_PARAMS,
                 rapi_only_aliases=CUSTOM_PARAMS,
                 exceptions=(IDENTIFIERS + NOT_EXPOSED_YET),
                 set_exceptions=FILLED_PARAMS)

739 740 741 742 743 744 745 746 747
  # Delete groups
  for group in [group1, group3]:
    (job_id, ) = _DoTests([
      ("/2/groups/%s" % group, _VerifyReturnsJob, "DELETE", None),
      ])

    _WaitForRapiJob(job_id)


748
def TestRapiInstanceAdd(node, use_client):
749
  """Test adding a new instance via RAPI"""
750 751
  if not qa_config.IsTemplateSupported(constants.DT_PLAIN):
    return
752
  instance = qa_config.AcquireInstance()
753
  instance.SetDiskTemplate(constants.DT_PLAIN)
754
  try:
755 756 757
    disks = [{"size": utils.ParseUnit(d.get("size")),
              "name": str(d.get("name"))}
             for d in qa_config.GetDiskOptions()]
758
    nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
759 760 761
    nics = [{
      constants.INIC_MAC: nic0_mac,
      }]
762

763
    beparams = {
764 765
      constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
      constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
766
      }
767

768
    if use_client:
769
      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
770
                                           instance.name,
771 772 773
                                           constants.DT_PLAIN,
                                           disks, nics,
                                           os=qa_config.get("os"),
774
                                           pnode=node.primary,
775 776 777
                                           beparams=beparams)
    else:
      body = {
778 779
        "__version__": 1,
        "mode": constants.INSTANCE_CREATE,
780
        "name": instance.name,
781
        "os_type": qa_config.get("os"),
782
        "disk_template": constants.DT_PLAIN,
783
        "pnode": node.primary,
784 785 786
        "beparams": beparams,
        "disks": disks,
        "nics": nics,
787 788 789 790 791
        }

      (job_id, ) = _DoTests([
        ("/2/instances", _VerifyReturnsJob, "POST", body),
        ])
792 793 794 795 796

    _WaitForRapiJob(job_id)

    return instance
  except:
797
    instance.Release()
798 799 800
    raise


801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870
def _GenInstanceAllocationDict(node, instance):
  """Creates an instance allocation dict to be used with the RAPI"""
  instance.SetDiskTemplate(constants.DT_PLAIN)

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

  nic0_mac = instance.GetNicMacAddr(0, constants.VALUE_GENERATE)
  nics = [{
    constants.INIC_MAC: nic0_mac,
    }]

  beparams = {
    constants.BE_MAXMEM: utils.ParseUnit(qa_config.get(constants.BE_MAXMEM)),
    constants.BE_MINMEM: utils.ParseUnit(qa_config.get(constants.BE_MINMEM)),
    }

  return _rapi_client.InstanceAllocation(constants.INSTANCE_CREATE,
                                         instance.name,
                                         constants.DT_PLAIN,
                                         disks, nics,
                                         os=qa_config.get("os"),
                                         pnode=node.primary,
                                         beparams=beparams)


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

  JOBS_KEY = "jobs"

  instance_one = qa_config.AcquireInstance()
  instance_two = qa_config.AcquireInstance()
  instance_list = [instance_one, instance_two]
  try:
    rapi_dicts = map(functools.partial(_GenInstanceAllocationDict, node),
                     instance_list)

    job_id = _rapi_client.InstancesMultiAlloc(rapi_dicts)

    results, = _WaitForRapiJob(job_id)

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

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

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

  return (instance_one, instance_two)


871
@InstanceCheck(None, INST_DOWN, FIRST_ARG)
872
def TestRapiInstanceRemove(instance, use_client):
873
  """Test removing instance via RAPI"""
874 875 876 877 878
  # FIXME: this does not work if LVM is not enabled. Find out if this is a bug
  # in RAPI or in the test
  if not qa_config.IsStorageTypeSupported(constants.ST_LVM_VG):
    return

879
  if use_client:
880
    job_id = _rapi_client.DeleteInstance(instance.name)
881 882
  else:
    (job_id, ) = _DoTests([
883
      ("/2/instances/%s" % instance.name, _VerifyReturnsJob, "DELETE", None),
884
      ])
885 886 887

  _WaitForRapiJob(job_id)

888

889
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
890 891
def TestRapiInstanceMigrate(instance):
  """Test migrating instance via RAPI"""
892
  if not IsMigrationSupported(instance):
893 894
    print qa_logging.FormatInfo("Instance doesn't support migration, skipping"
                                " test")
895
    return
896
  # Move to secondary node
897
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
898
  qa_utils.RunInstanceCheck(instance, True)
899
  # And back to previous primary
900
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance.name))
901 902


903
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
904 905
def TestRapiInstanceFailover(instance):
  """Test failing over instance via RAPI"""
906
  if not IsFailoverSupported(instance):
907 908
    print qa_logging.FormatInfo("Instance doesn't support failover, skipping"
                                " test")
909
    return
910
  # Move to secondary node
911
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
912
  qa_utils.RunInstanceCheck(instance, True)
913
  # And back to previous primary
914
  _WaitForRapiJob(_rapi_client.FailoverInstance(instance.name))
915 916


917
@InstanceCheck(INST_UP, INST_DOWN, FIRST_ARG)
918 919
def TestRapiInstanceShutdown(instance):
  """Test stopping an instance via RAPI"""
920
  _WaitForRapiJob(_rapi_client.ShutdownInstance(instance.name))
921 922


923
@InstanceCheck(INST_DOWN, INST_UP, FIRST_ARG)
924 925
def TestRapiInstanceStartup(instance):
  """Test starting an instance via RAPI"""
926
  _WaitForRapiJob(_rapi_client.StartupInstance(instance.name))
927 928


929
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
Iustin Pop's avatar
Iustin Pop committed
930 931 932 933 934 935 936
def TestRapiInstanceRenameAndBack(rename_source, rename_target):
  """Test renaming instance via RAPI

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

  """
937
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_source, rename_target))
938
  qa_utils.RunInstanceCheck(rename_source, False)
939
  qa_utils.RunInstanceCheck(rename_target, False)
Iustin Pop's avatar
Iustin Pop committed
940
  _WaitForRapiJob(_rapi_client.RenameInstance(rename_target, rename_source))
941
  qa_utils.RunInstanceCheck(rename_target, False)
942 943


944
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
945 946
def TestRapiInstanceReinstall(instance):
  """Test reinstalling an instance via RAPI"""
947
  if instance.disk_template == constants.DT_DISKLESS:
948
    print qa_logging.FormatInfo("Test not supported for diskless instances")
949 950
    return

951
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name))
952 953 954 955
  # By default, the instance is started again
  qa_utils.RunInstanceCheck(instance, True)

  # Reinstall again without starting
956
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance.name,
957
                                                 no_startup=True))
958 959


960
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
961 962
def TestRapiInstanceReplaceDisks(instance):
  """Test replacing instance disks via RAPI"""
963
  if not IsDiskReplacingSupported(instance):
964 965
    print qa_logging.FormatInfo("Instance doesn't support disk replacing,"
                                " skipping test")
966
    return
Iustin Pop's avatar
Iustin Pop committed
967
  fn = _rapi_client.ReplaceInstanceDisks
968
  _WaitForRapiJob(fn(instance.name,
Iustin Pop's avatar
Iustin Pop committed
969
                     mode=constants.REPLACE_DISK_AUTO, disks=[]))
970
  _WaitForRapiJob(fn(instance.name,
Iustin Pop's avatar
Iustin Pop committed
971
                     mode=constants.REPLACE_DISK_SEC, disks="0"))
972 973


974
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
975 976
def TestRapiInstanceModify(instance):
  """Test modifying instance via RAPI"""
977 978
  default_hv = qa_config.GetDefaultHypervisor()

979
  def _ModifyInstance(**kwargs):
980
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance.name, **kwargs))
981 982 983 984 985 986 987 988 989

  _ModifyInstance(beparams={
    constants.BE_VCPUS: 3,
    })

  _ModifyInstance(beparams={
    constants.BE_VCPUS: constants.VALUE_DEFAULT,
    })

990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
  if default_hv == constants.HT_XEN_PVM:
    _ModifyInstance(hvparams={
      constants.HV_KERNEL_ARGS: "single",
      })
    _ModifyInstance(hvparams={
      constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
      })
  elif default_hv == constants.HT_XEN_HVM:
    _ModifyInstance(hvparams={
      constants.HV_BOOT_ORDER: "acn",
      })
    _ModifyInstance(hvparams={
      constants.HV_BOOT_ORDER: constants.VALUE_DEFAULT,
      })
1004 1005


1006
@InstanceCheck(INST_UP, INST_UP, FIRST_ARG)
1007 1008
def TestRapiInstanceConsole(instance):
  """Test getting instance console information via RAPI"""
1009
  result = _rapi_client.GetInstanceConsole(instance.name)
1010
  console = objects.InstanceConsole.FromDict(result)
Jose A. Lopes's avatar
Jose A. Lopes committed
1011
  AssertEqual(console.Validate(), None)
1012
  AssertEqual(console.instance, qa_utils.ResolveInstanceName(instance.name))
1013 1014


1015
@InstanceCheck(INST_DOWN, INST_DOWN, FIRST_ARG)
1016 1017 1018
def TestRapiStoppedInstanceConsole(instance):
  """Test getting stopped instance's console information via RAPI"""
  try:
1019
    _rapi_client.GetInstanceConsole(instance.name)
1020 1021 1022 1023 1024 1025 1026
  except rapi.client.GanetiApiError, err:
    AssertEqual(err.code, 503)
  else:
    raise qa_error.Error("Getting console for stopped instance didn't"
                         " return HTTP 503")


1027 1028 1029 1030 1031 1032 1033
def GetOperatingSystems():
  """Retrieves a list of all available operating systems.

  """
  return _rapi_client.GetOperatingSystems()


1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
def _InvokeMoveInstance(current_dest_inst, current_src_inst, rapi_pw_filename,
                        joint_master, perform_checks, target_nodes=None):
  """ Invokes the move-instance tool for testing purposes.

  """
  # Some uses of this test might require that RAPI-only commands are used,
  # and the checks are command-line based.
  if perform_checks:
    qa_utils.RunInstanceCheck(current_dest_inst, False)

  cmd = [
      "../tools/move-instance",
      "--verbose",
      "--src-ca-file=%s" % _rapi_ca.name,
      "--src-username=%s" % _rapi_username,
      "--src-password-file=%s" % rapi_pw_filename,
      "--dest-instance-name=%s" % current_dest_inst,
      ]

  if target_nodes:
    pnode, snode = target_nodes
    cmd.extend([
      "--dest-primary-node=%s" % pnode,
      "--dest-secondary-node=%s" % snode,
      ])
  else:
1060 1061 1062 1063
    cmd.extend([
      "--iallocator=%s" % constants.IALLOC_HAIL,
      "--opportunistic-tries=1",
      ])
1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078

  cmd.extend([
    "--net=0:mac=%s" % constants.VALUE_GENERATE,
    joint_master,
    joint_master,
    current_src_inst,
    ])

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

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


1079
def TestInterClusterInstanceMove(src_instance, dest_instance,
1080
                                 inodes, tnode, perform_checks=True):
1081 1082 1083 1084 1085 1086 1087
  """Test tools/move-instance"""
  master = qa_config.GetMasterNode()

  rapi_pw_file = tempfile.NamedTemporaryFile()
  rapi_pw_file.write(_rapi_password)
  rapi_pw_file.flush()

1088 1089 1090
  # Needed only if checks are to be performed
  if perform_checks:
    dest_instance.SetDiskTemplate(src_instance.disk_template)
1091

1092
  # TODO: Run some instance tests before moving back
Iustin Pop's avatar
Iustin Pop committed
1093

1094 1095 1096 1097 1098 1099
  if len(inodes) > 1:
    # No disk template currently requires more than 1 secondary node. If this
    # changes, either this test must be skipped or the script must be updated.
    assert len(inodes) == 2
    snode = inodes[1]
  else:
1100
    # Instance is not redundant, but we still need to pass a node
1101
    # (which will be ignored)
1102 1103
    snode = tnode
  pnode = inodes[0]
1104

1105 1106 1107 1108 1109
  # pnode:snode are the *current* nodes, and the first move is an
  # iallocator-guided move outside of pnode. The node lock for the pnode
  # assures that this happens, and while we cannot be sure where the instance
  # will land, it is a real move.
  locks = {locking.LEVEL_NODE: [pnode.primary]}
1110
  RunWithLocks(_InvokeMoveInstance, locks, 600.0, False,
1111 1112
               dest_instance.name, src_instance.name, rapi_pw_file.name,
               master.primary, perform_checks)
1113

1114 1115 1116 1117
  # And then back to pnode:snode
  _InvokeMoveInstance(src_instance.name, dest_instance.name, rapi_pw_file.name,
                      master.primary, perform_checks,
                      target_nodes=(pnode.primary, snode.primary))
Niklas Hambuechen's avatar
Niklas Hambuechen committed
1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165


def TestFilters():
  """Testing filter management via the remote API.

  """

  body = {
    "priority": 10,
    "predicates": [],
    "action": "CONTINUE",
    "reason": [(constants.OPCODE_REASON_SRC_USER,
               "reason1",
               utils.EpochNano())],
  }

  body1 = copy.deepcopy(body)
  body1["priority"] = 20

  # Query filters
  _DoTests([("/2/filters", [], "GET", None)])

  # Add a filter via POST and delete it again
  uuid = _DoTests([("/2/filters", None, "POST", body)])[0]
  uuid_module.UUID(uuid)  # Check if uuid is a valid UUID
  _DoTests([("/2/filters/%s" % uuid, lambda r: r is None, "DELETE", None)])

  _DoTests([
    # Check PUT-inserting a nonexistent filter with given UUID
    ("/2/filters/%s" % uuid, lambda u: u == uuid, "PUT", body),
    # Check PUT-inserting an existent filter with given UUID
    ("/2/filters/%s" % uuid, lambda u: u == uuid, "PUT", body1),
    # Check that the update changed the filter
    ("/2/filters/%s" % uuid, lambda f: f["priority"] == 20, "GET", None),
    # Delete it again
    ("/2/filters/%s" % uuid, lambda r: r is None, "DELETE", None),
    ])

  # Add multiple filters, query and delete them
  uuids = _DoTests([
    ("/2/filters", None, "POST", body),
    ("/2/filters", None, "POST", body),
    ("/2/filters", None, "POST", body),
    ])
  _DoTests([("/2/filters", lambda rs: [r["uuid"] for r in rs] == uuids,
             "GET", None)])
  for u in uuids:
    _DoTests([("/2/filters/%s" % u, lambda r: r is None, "DELETE", None)])