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

Iustin Pop's avatar
Iustin Pop committed
3
# Copyright (C) 2007, 2008, 2009, 2010 Google Inc.
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Remote API QA tests.

"""

Michael Hanselmann's avatar
Michael Hanselmann committed
25
import tempfile
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
26
27
28
29
30

from ganeti import utils
from ganeti import constants
from ganeti import errors
from ganeti import serializer
Michael Hanselmann's avatar
Michael Hanselmann committed
31
32
33
34
35
from ganeti import cli
from ganeti import rapi

import ganeti.rapi.client
import ganeti.rapi.client_utils
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
36
37
38
39
40

import qa_config
import qa_utils
import qa_error

41
from qa_utils import (AssertEqual, AssertNotEqual, AssertIn, AssertMatch,
42
                      StartLocalCommand)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
43
44


Michael Hanselmann's avatar
Michael Hanselmann committed
45
46
_rapi_ca = None
_rapi_client = None
47
48
_rapi_username = None
_rapi_password = None
49
50


Michael Hanselmann's avatar
Michael Hanselmann committed
51
52
def Setup(username, password):
  """Configures the RAPI client.
53

Michael Hanselmann's avatar
Michael Hanselmann committed
54
55
56
  """
  global _rapi_ca
  global _rapi_client
57
58
59
60
61
  global _rapi_username
  global _rapi_password

  _rapi_username = username
  _rapi_password = password
62

Michael Hanselmann's avatar
Michael Hanselmann committed
63
  master = qa_config.GetMasterNode()
64

Michael Hanselmann's avatar
Michael Hanselmann committed
65
66
  # Load RAPI certificate from master node
  cmd = ["cat", constants.RAPI_CERT_FILE]
67

Michael Hanselmann's avatar
Michael Hanselmann committed
68
69
70
71
72
  # Write to temporary file
  _rapi_ca = tempfile.NamedTemporaryFile()
  _rapi_ca.write(qa_utils.GetCommandOutput(master["primary"],
                                           utils.ShellQuoteArgs(cmd)))
  _rapi_ca.flush()
73

Michael Hanselmann's avatar
Michael Hanselmann committed
74
  port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
75
76
  cfg_curl = rapi.client.GenericCurlConfig(cafile=_rapi_ca.name,
                                           proxy="")
77

Michael Hanselmann's avatar
Michael Hanselmann committed
78
79
80
  _rapi_client = rapi.client.GanetiRapiClient(master["primary"], port=port,
                                              username=username,
                                              password=password,
81
                                              curl_config_fn=cfg_curl)
82

Michael Hanselmann's avatar
Michael Hanselmann committed
83
  print "RAPI protocol version: %s" % _rapi_client.GetVersion()
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
84
85
86


INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
87
88
                   "admin_state",
                   "disk_template", "disk.sizes",
89
                   "nic.ips", "nic.macs", "nic.modes", "nic.links",
90
                   "beparams", "hvparams",
91
                   "oper_state", "oper_ram", "oper_vcpus", "status", "tags")
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
92
93
94
95
96

NODE_FIELDS = ("name", "dtotal", "dfree",
               "mtotal", "mnode", "mfree",
               "pinst_cnt", "sinst_cnt", "tags")

97
98
99
100
101
102
JOB_FIELDS = frozenset([
  "id", "ops", "status", "summary",
  "opstatus", "opresult", "oplog",
  "received_ts", "start_ts", "end_ts",
  ])

Iustin Pop's avatar
Iustin Pop committed
103
LIST_FIELDS = ("id", "uri")
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
104
105
106
107
108
109


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

  """
110
  return qa_config.TestEnabled('rapi')
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
111
112
113


def _DoTests(uris):
114
  results = []
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
115

116
  for uri, verify, method, body in uris:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
117
118
    assert uri.startswith("/")

119
    print "%s %s" % (method, uri)
Michael Hanselmann's avatar
Michael Hanselmann committed
120
    data = _rapi_client._SendRequest(method, uri, None, body)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
121
122
123
124
125
126
127

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

128
    results.append(data)
129
130
131
132
133
134
135

  return results


def _VerifyReturnsJob(data):
  AssertMatch(data, r'^\d+$')

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
136
137
138
139
140
141

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

  """
  _DoTests([
142
    ("/version", constants.RAPI_VERSION, 'GET', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
143
144
145
146
147
148
149
    ])


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

  """
150
151
  master = qa_config.GetMasterNode()
  master_full = qa_utils.ResolveNodeName(master)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
152
153
154
155

  def _VerifyInfo(data):
    AssertIn("name", data)
    AssertIn("master", data)
156
    AssertEqual(data["master"], master_full)
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
157
158
159

  def _VerifyNodes(data):
    master_entry = {
160
161
      "id": master_full,
      "uri": "/2/nodes/%s" % master_full,
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
162
163
164
165
166
167
168
169
170
      }
    AssertIn(master_entry, data)

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

  _DoTests([
171
172
173
174
175
176
177
178
    ("/", 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/instances", [], 'GET', None),
    ("/2/instances?bulk=1", [], 'GET', None),
    ("/2/os", None, 'GET', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
179
180
181
182
183
184
185
186
187
188
    ])


def TestInstance(instance):
  """Testing getting instance(s) info via remote API.

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
190
191
  def _VerifyInstancesList(data):
    for instance in data:
192
      for entry in LIST_FIELDS:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
193
        AssertIn(entry, instance)
194

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
195
196
197
198
199
  def _VerifyInstancesBulk(data):
    for instance_data in data:
      _VerifyInstance(instance_data)

  _DoTests([
200
201
202
203
204
205
206
    ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET', None),
    ("/2/instances", _VerifyInstancesList, 'GET', None),
    ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET', None),
    ("/2/instances/%s/activate-disks" % instance["name"],
     _VerifyReturnsJob, 'PUT', None),
    ("/2/instances/%s/deactivate-disks" % instance["name"],
     _VerifyReturnsJob, 'PUT', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
207
208
    ])

209
210
211
212
213
214
215
216
217
218
219
220
221
  # Test OpPrepareExport
  (job_id, ) = _DoTests([
    ("/2/instances/%s/prepare-export?mode=%s" %
     (instance["name"], constants.EXPORT_MODE_REMOTE),
     _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
222
223
224
225
226
227
228
229

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

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
231
232
  def _VerifyNodesList(data):
    for node in data:
233
      for entry in LIST_FIELDS:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
234
        AssertIn(entry, node)
235

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
236
237
238
239
240
  def _VerifyNodesBulk(data):
    for node_data in data:
      _VerifyNode(node_data)

  _DoTests([
241
242
243
    ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET', None),
    ("/2/nodes", _VerifyNodesList, 'GET', None),
    ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
244
245
246
247
248
249
250
251
    ])


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

  """
  if kind == constants.TAG_CLUSTER:
252
    uri = "/2/tags"
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
253
  elif kind == constants.TAG_NODE:
254
    uri = "/2/nodes/%s/tags" % name
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
255
  elif kind == constants.TAG_INSTANCE:
256
    uri = "/2/instances/%s/tags" % name
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
257
258
259
260
  else:
    raise errors.ProgrammerError("Unknown tag kind")

  def _VerifyTags(data):
261
    AssertEqual(sorted(tags), sorted(data))
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
262

263
264
265
266
267
268
269
270
271
  query = "&".join("tag=%s" % i for i in tags)

  # Add tags
  (job_id, ) = _DoTests([
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "PUT", None),
    ])
  _WaitForRapiJob(job_id)

  # Retrieve tags
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
272
  _DoTests([
273
    (uri, _VerifyTags, 'GET', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
274
    ])
275

276
277
278
279
280
281
  # Remove tags
  (job_id, ) = _DoTests([
    ("%s?%s" % (uri, query), _VerifyReturnsJob, "DELETE", None),
    ])
  _WaitForRapiJob(job_id)

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297

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

  """
  master = qa_config.GetMasterNode()

  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),
    ])

298
299
  return rapi.client_utils.PollJob(_rapi_client, job_id,
                                   cli.StdioJobPollReportCb())
300
301


302
def TestRapiInstanceAdd(node, use_client):
303
304
305
  """Test adding a new instance via RAPI"""
  instance = qa_config.AcquireInstance()
  try:
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    memory = utils.ParseUnit(qa_config.get("mem"))
    disk_sizes = [utils.ParseUnit(size) for size in qa_config.get("disk")]

    if use_client:
      disks = [{"size": size} for size in disk_sizes]
      nics = [{}]

      beparams = {
        constants.BE_MEMORY: memory,
        }

      job_id = _rapi_client.CreateInstance(constants.INSTANCE_CREATE,
                                           instance["name"],
                                           constants.DT_PLAIN,
                                           disks, nics,
                                           os=qa_config.get("os"),
                                           pnode=node["primary"],
                                           beparams=beparams)
    else:
      body = {
        "name": instance["name"],
        "os": qa_config.get("os"),
        "disk_template": constants.DT_PLAIN,
        "pnode": node["primary"],
        "memory": memory,
        "disks": disk_sizes,
        }

      (job_id, ) = _DoTests([
        ("/2/instances", _VerifyReturnsJob, "POST", body),
        ])
337
338
339
340
341
342
343
344
345

    _WaitForRapiJob(job_id)

    return instance
  except:
    qa_config.ReleaseInstance(instance)
    raise


346
def TestRapiInstanceRemove(instance, use_client):
347
  """Test removing instance via RAPI"""
348
349
350
351
352
353
  if use_client:
    job_id = _rapi_client.DeleteInstance(instance["name"])
  else:
    (job_id, ) = _DoTests([
      ("/2/instances/%s" % instance["name"], _VerifyReturnsJob, "DELETE", None),
      ])
354
355
356
357

  _WaitForRapiJob(job_id)

  qa_config.ReleaseInstance(instance)
358
359


360
361
362
363
364
365
366
367
def TestRapiInstanceMigrate(instance):
  """Test migrating instance via RAPI"""
  # Move to secondary node
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))
  # And back to previous primary
  _WaitForRapiJob(_rapi_client.MigrateInstance(instance["name"]))


368
369
370
371
372
373
374
375
376
def TestRapiInstanceRename(instance, rename_target):
  """Test renaming instance via RAPI"""
  rename_source = instance["name"]

  for name1, name2 in [(rename_source, rename_target),
                       (rename_target, rename_source)]:
    _WaitForRapiJob(_rapi_client.RenameInstance(name1, name2))


377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
def TestRapiInstanceModify(instance):
  """Test modifying instance via RAPI"""
  def _ModifyInstance(**kwargs):
    _WaitForRapiJob(_rapi_client.ModifyInstance(instance["name"], **kwargs))

  _ModifyInstance(hvparams={
    constants.HV_KERNEL_ARGS: "single",
    })

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

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

  _ModifyInstance(hvparams={
    constants.HV_KERNEL_ARGS: constants.VALUE_DEFAULT,
    })


399
400
def TestInterClusterInstanceMove(src_instance, dest_instance,
                                 pnode, snode, tnode):
401
402
403
404
405
406
407
408
  """Test tools/move-instance"""
  master = qa_config.GetMasterNode()

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

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

410
411
412
413
414
415
416
417
  if snode is None:
    # instance is not redundant, but we still need to pass a node
    # (which will be ignored)
    fsec = tnode
  else:
    fsec = snode
  # note: pnode:snode are the *current* nodes, so we move it first to
  # tnode:pnode, then back to pnode:snode
Iustin Pop's avatar
Iustin Pop committed
418
  for si, di, pn, sn in [(src_instance["name"], dest_instance["name"],
419
                          tnode["primary"], pnode["primary"]),
Iustin Pop's avatar
Iustin Pop committed
420
                         (dest_instance["name"], src_instance["name"],
421
                          pnode["primary"], fsec["primary"])]:
422
423
424
425
426
427
    cmd = [
      "../tools/move-instance",
      "--verbose",
      "--src-ca-file=%s" % _rapi_ca.name,
      "--src-username=%s" % _rapi_username,
      "--src-password-file=%s" % rapi_pw_file.name,
Iustin Pop's avatar
Iustin Pop committed
428
429
430
      "--dest-instance-name=%s" % di,
      "--dest-primary-node=%s" % pn,
      "--dest-secondary-node=%s" % sn,
431
      "--net=0:mac=%s" % constants.VALUE_GENERATE,
432
433
      master["primary"],
      master["primary"],
Iustin Pop's avatar
Iustin Pop committed
434
      si,
435
436
437
      ]

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