Statistics
| Branch: | Tag: | Revision:

root / qa / rapi-workload.py @ 6b710ec0

History | View | Annotate | Download (14.6 kB)

1
#!/usr/bin/python -u
2
#
3

    
4
# Copyright (C) 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
"""Script for providing a large amount of RAPI calls to Ganeti.
23

24
"""
25

    
26
# pylint: disable=C0103
27
# due to invalid name
28

    
29

    
30
import sys
31

    
32
import ganeti.constants as constants
33
from ganeti.rapi.client import GanetiApiError
34

    
35
import qa_config
36
import qa_node
37
import qa_rapi
38

    
39

    
40
# The purpose of this file is to provide a stable and extensive RAPI workload
41
# that manipulates the cluster only using RAPI commands, with the assumption
42
# that an empty cluster was set up beforehand. All the nodes that can be added
43
# to the cluster should be a part of it, and no instances should be present.
44
#
45
# Its intended use is in RAPI compatibility tests, where different versions with
46
# possibly vastly different QAs must be compared. Running the QA on both
47
# versions of the cluster will produce RAPI calls, but there is no guarantee
48
# that they will match, or that functions invoked in between will not change the
49
# results.
50
#
51
# By using only RAPI functions, we are sure to be able to capture and log all
52
# the changes in cluster state, and be able to compare them afterwards.
53
#
54
# The functionality of the QA is still used to generate a functioning,
55
# RAPI-enabled cluster, and to set up a C{GanetiRapiClient} capable of issuing
56
# commands to the cluster.
57
#
58
# Due to the fact that not all calls issued as a part of the workload might be
59
# implemented in the different versions of Ganeti, the client does not halt or
60
# produce a non-zero exit code upon encountering a RAPI error. Instead, it
61
# reports it and moves on. Any utility comparing the requests should account for
62
# this.
63

    
64

    
65
def MockMethod(*_args, **_kwargs):
66
  """ Absorbs all arguments, does nothing, returns None.
67

68
  """
69
  return None
70

    
71

    
72
def InvokerCreator(fn, name):
73
  """ Returns an invoker function that will invoke the given function
74
  with any arguments passed to the invoker at a later time, while
75
  catching any specific non-fatal errors we would like to know more
76
  about.
77

78
  @type fn arbitrary function
79
  @param fn The function to invoke later.
80
  @type name string
81
  @param name The name of the function, for debugging purposes.
82
  @rtype function
83

84
  """
85
  def decoratedFn(*args, **kwargs):
86
    result = None
87
    try:
88
      print "Using method %s" % name
89
      result = fn(*args, **kwargs)
90
    except GanetiApiError as e:
91
      print "RAPI error while performing function %s : %s" % \
92
            (name, str(e))
93
    return result
94

    
95
  return decoratedFn
96

    
97

    
98
RAPI_USERNAME = "ganeti-qa"
99

    
100

    
101
class GanetiRapiClientWrapper(object):
102
  """ Creates and initializes a GanetiRapiClient, and acts as a wrapper invoking
103
  only the methods that the version of the client actually uses.
104

105
  """
106
  def __init__(self):
107
    self._client = qa_rapi.Setup(RAPI_USERNAME,
108
                                 qa_rapi.LookupRapiSecret(RAPI_USERNAME))
109

    
110
  def __getattr__(self, attr):
111
    """ Fetches an attribute from the underlying client if necessary.
112

113
    """
114
    # Assuming that this method exposes no public methods of its own,
115
    # and that any private methods are named according to the style
116
    # guide, this will stop infinite loops in attribute fetches.
117
    if attr.startswith("_"):
118
      return self.__getattribute__(attr)
119
    try:
120
      return InvokerCreator(self._client.__getattribute__(attr), attr)
121
    except AttributeError:
122
      print "Missing method %s; supplying mock method" % attr
123
      return MockMethod
124

    
125

    
126
def Finish(client, fn, *args, **kwargs):
127
  """ When invoked with a job-starting RAPI client method, it passes along any
128
  additional arguments and waits until its completion.
129

130
  @type client C{GanetiRapiClientWrapper}
131
  @param client The client wrapper.
132
  @type fn function
133
  @param fn A client method returning a job id.
134

135
  """
136
  possible_job_id = fn(*args, **kwargs)
137
  try:
138
    # The job ids are returned as both ints and ints represented by strings.
139
    # This is a pythonic check to see if the content is an int.
140
    int(possible_job_id)
141
  except (ValueError, TypeError):
142
    # As a rule of thumb, failures will return None, and other methods are
143
    # expected to return at least something
144
    if possible_job_id is not None:
145
      print ("Finish called with a method not producing a job id, "
146
             "returning %s" % possible_job_id)
147
    return possible_job_id
148

    
149
  success = client.WaitForJobCompletion(possible_job_id)
150

    
151
  result = client.GetJobStatus(possible_job_id)["opresult"][0]
152
  if success:
153
    return result
154
  else:
155
    print "Error encountered while performing operation: "
156
    print result
157
    return None
158

    
159

    
160
def TestTags(client, get_fn, add_fn, delete_fn, *args):
161
  """ Tests whether tagging works.
162

163
  @type client C{GanetiRapiClientWrapper}
164
  @param client The client wrapper.
165
  @type get_fn function
166
  @param get_fn A Get*Tags function of the client.
167
  @type add_fn function
168
  @param add_fn An Add*Tags function of the client.
169
  @type delete_fn function
170
  @param delete_fn A Delete*Tags function of the client.
171

172
  To allow this method to work for all tagging functions of the client, use
173
  named methods.
174

175
  """
176
  get_fn(*args)
177

    
178
  tags = ["tag1", "tag2", "tag3"]
179
  Finish(client, add_fn, *args, tags=tags, dry_run=True)
180
  Finish(client, add_fn, *args, tags=tags)
181

    
182
  get_fn(*args)
183

    
184
  Finish(client, delete_fn, *args, tags=tags[:1], dry_run=True)
185
  Finish(client, delete_fn, *args, tags=tags[:1])
186

    
187
  get_fn(*args)
188

    
189
  Finish(client, delete_fn, *args, tags=tags[1:])
190

    
191
  get_fn(*args)
192

    
193

    
194
def TestGetters(client):
195
  """ Tests the various get functions which only retrieve information about the
196
  cluster.
197

198
  @type client C{GanetiRapiClientWrapper}
199

200
  """
201
  client.GetVersion()
202
  client.GetFeatures()
203
  client.GetOperatingSystems()
204
  client.GetInfo()
205
  client.GetClusterTags()
206
  client.GetInstances()
207
  client.GetInstances(bulk=True)
208
  client.GetJobs()
209
  client.GetJobs(bulk=True)
210
  client.GetNodes()
211
  client.GetNodes(bulk=True)
212
  client.GetNetworks()
213
  client.GetNetworks(bulk=True)
214
  client.GetGroups()
215
  client.GetGroups(bulk=True)
216

    
217

    
218
def RemoveAllInstances(client):
219
  """ Queries for a list of instances, then removes them all.
220

221
  @type client C{GanetiRapiClientWrapper}
222
  @param client A wrapped RAPI client.
223

224
  """
225
  instances = client.GetInstances()
226
  for inst in instances:
227
    Finish(client, client.DeleteInstance, inst)
228

    
229
  instances = client.GetInstances()
230
  assert len(instances) == 0
231

    
232

    
233
def TestSingleInstance(client, instance_name, alternate_name, node_one,
234
                       node_two):
235
  """ Creates an instance, performs operations involving it, and then deletes
236
  it.
237

238
  @type client C{GanetiRapiClientWrapper}
239
  @param client A wrapped RAPI client.
240
  @type instance_name string
241
  @param instance_name The hostname to use.
242
  @type instance_name string
243
  @param instance_name Another valid hostname to use.
244
  @type node_one string
245
  @param node_one A node on which an instance can be added.
246
  @type node_two string
247
  @param node_two A node on which an instance can be added.
248

249
  """
250

    
251
  # Check that a dry run works, use string with size and unit
252
  Finish(client, client.CreateInstance,
253
         "create", instance_name, "plain", [{"size":"1gb"}], [], dry_run=True,
254
          os="debian-image", pnode=node_one)
255

    
256
  # Another dry run, numeric size, should work, but still a dry run
257
  Finish(client, client.CreateInstance,
258
         "create", instance_name, "plain", [{"size": "1000"}], [{}],
259
         dry_run=True, os="debian-image", pnode=node_one)
260

    
261
  # Create a smaller instance, and delete it immediately
262
  Finish(client, client.CreateInstance,
263
         "create", instance_name, "plain", [{"size":800}], [{}],
264
         os="debian-image", pnode=node_one)
265

    
266
  Finish(client, client.DeleteInstance, instance_name)
267

    
268
  # Create one instance to use in further tests
269
  Finish(client, client.CreateInstance,
270
         "create", instance_name, "plain", [{"size":1200}], [{}],
271
         os="debian-image", pnode=node_one)
272

    
273
  client.GetInstance(instance_name)
274

    
275
  Finish(client, client.GetInstanceInfo, instance_name)
276

    
277
  Finish(client, client.GetInstanceInfo, instance_name, static=True)
278

    
279
  TestTags(client, client.GetInstanceTags, client.AddInstanceTags,
280
           client.DeleteInstanceTags, instance_name)
281

    
282
  Finish(client, client.GrowInstanceDisk,
283
         instance_name, 0, 100, wait_for_sync=True)
284

    
285
  Finish(client, client.RebootInstance,
286
         instance_name, "soft", ignore_secondaries=True, dry_run=True,
287
         reason="Hulk smash gently!")
288

    
289
  Finish(client, client.ShutdownInstance,
290
         instance_name, dry_run=True, no_remember=False,
291
         reason="Hulk smash hard!")
292

    
293
  Finish(client, client.StartupInstance,
294
         instance_name, dry_run=True, no_remember=False,
295
         reason="Not hard enough!")
296

    
297
  Finish(client, client.RebootInstance,
298
         instance_name, "soft", ignore_secondaries=True, dry_run=False)
299

    
300
  Finish(client, client.ShutdownInstance,
301
         instance_name, dry_run=False, no_remember=False)
302

    
303
  Finish(client, client.ModifyInstance,
304
         instance_name, disk_template="drbd", remote_node=node_two)
305

    
306
  Finish(client, client.ModifyInstance,
307
         instance_name, disk_template="plain")
308

    
309
  Finish(client, client.RenameInstance,
310
         instance_name, alternate_name, ip_check=True, name_check=True)
311

    
312
  Finish(client, client.RenameInstance, alternate_name, instance_name)
313

    
314
  Finish(client, client.DeactivateInstanceDisks, instance_name)
315

    
316
  Finish(client, client.ActivateInstanceDisks, instance_name)
317

    
318
  # Note that the RecreateInstanceDisks command will always fail, as there is
319
  # no way to induce the necessary prerequisites (removal of LV) via RAPI.
320
  # Keeping it around allows us to at least know that it still exists.
321
  Finish(client, client.RecreateInstanceDisks,
322
         instance_name, [0], [node_one])
323

    
324
  Finish(client, client.StartupInstance,
325
         instance_name, dry_run=False, no_remember=False)
326

    
327
  client.GetInstanceConsole(instance_name)
328

    
329
  Finish(client, client.ReinstallInstance,
330
         instance_name, os=None, no_startup=False, osparams={})
331

    
332
  Finish(client, client.DeleteInstance, instance_name, dry_run=True)
333

    
334
  Finish(client, client.DeleteInstance, instance_name)
335

    
336

    
337
def MarkUnmarkNode(client, node, state):
338
  """ Given a certain node state, marks a node as being in that state, and then
339
  unmarks it.
340

341
  @type client C{GanetiRapiClientWrapper}
342
  @param client A wrapped RAPI client.
343
  @type node string
344
  @type state string
345

346
  """
347
  # pylint: disable=W0142
348
  Finish(client, client.ModifyNode, node, **{state: True})
349
  Finish(client, client.ModifyNode, node, **{state: False})
350
  # pylint: enable=W0142
351

    
352

    
353
def TestNodeOperations(client, non_master_node):
354
  """ Tests various operations related to nodes only
355

356
  @type client C{GanetiRapiClientWrapper}
357
  @param client A wrapped RAPI client.
358
  @type non_master_node string
359
  @param non_master_node The name of a non-master node in the cluster.
360

361
  """
362

    
363
  client.GetNode(non_master_node)
364

    
365
  old_role = client.GetNodeRole(non_master_node)
366

    
367
  # Should fail
368
  Finish(client, client.SetNodeRole,
369
         non_master_node, "master", False, auto_promote=True)
370

    
371
  Finish(client, client.SetNodeRole,
372
         non_master_node, "regular", False, auto_promote=True)
373

    
374
  Finish(client, client.SetNodeRole,
375
         non_master_node, "master-candidate", False, auto_promote=True)
376

    
377
  Finish(client, client.SetNodeRole,
378
         non_master_node, "drained", False, auto_promote=True)
379

    
380
  Finish(client, client.SetNodeRole,
381
         non_master_node, old_role, False, auto_promote=True)
382

    
383
  Finish(client, client.PowercycleNode,
384
         non_master_node, force=False)
385

    
386
  storage_units_fields = [
387
    "name", "allocatable", "free", "node", "size", "type", "used",
388
  ]
389

    
390
  for storage_type in constants.STS_REPORT:
391
    storage_units = Finish(client, client.GetNodeStorageUnits,
392
                           non_master_node, storage_type,
393
                           ",".join(storage_units_fields))
394

    
395
    if len(storage_units) > 0 and len(storage_units[0]) > 0:
396
      # Name is the first entry of the first result, allocatable the other
397
      unit_name = storage_units[0][0]
398
      Finish(client, client.ModifyNodeStorageUnits,
399
             non_master_node, storage_type, unit_name,
400
             allocatable=not storage_units[0][1])
401
      Finish(client, client.ModifyNodeStorageUnits,
402
             non_master_node, storage_type, unit_name,
403
             allocatable=storage_units[0][1])
404
      Finish(client, client.RepairNodeStorageUnits,
405
             non_master_node, storage_type, unit_name)
406

    
407
  MarkUnmarkNode(client, non_master_node, "drained")
408
  MarkUnmarkNode(client, non_master_node, "powered")
409
  MarkUnmarkNode(client, non_master_node, "offline")
410

    
411

    
412
def Workload(client):
413
  """ The actual RAPI workload used for tests.
414

415
  @type client C{GanetiRapiClientWrapper}
416
  @param client A wrapped RAPI client.
417

418
  """
419

    
420
  # First just the simple information retrievals
421
  TestGetters(client)
422

    
423
  # Then the only remaining function which is parameter-free
424
  Finish(client, client.RedistributeConfig)
425

    
426
  TestTags(client, client.GetClusterTags, client.AddClusterTags,
427
           client.DeleteClusterTags)
428

    
429
  # Generously assume the master is present
430
  node = qa_config.AcquireNode()
431
  TestTags(client, client.GetNodeTags, client.AddNodeTags,
432
           client.DeleteNodeTags, node.primary)
433
  node.Release()
434

    
435
  # Instance tests
436

    
437
  # First remove all instances the QA might have created
438
  RemoveAllInstances(client)
439

    
440
  nodes = qa_config.AcquireManyNodes(2)
441
  instance_one = qa_config.AcquireInstance()
442
  instance_two = qa_config.AcquireInstance()
443
  TestSingleInstance(client, instance_one.name, instance_two.name,
444
                     nodes[0].primary, nodes[1].primary)
445
  instance_two.Release()
446
  instance_one.Release()
447
  qa_config.ReleaseManyNodes(nodes)
448

    
449
  node = qa_config.AcquireNode(exclude=qa_config.GetMasterNode())
450
  TestNodeOperations(client, node.primary)
451
  node.Release()
452

    
453

    
454
def Usage():
455
  sys.stderr.write("Usage:\n\trapi-workload.py qa-config-file")
456

    
457

    
458
def Main():
459
  if len(sys.argv) < 2:
460
    Usage()
461

    
462
  qa_config.Load(sys.argv[1])
463

    
464
  # Only the master will be present after a fresh QA cluster setup, so we have
465
  # to invoke this to get all the other nodes.
466
  qa_node.TestNodeAddAll()
467

    
468
  client = GanetiRapiClientWrapper()
469

    
470
  Workload(client)
471

    
472
  qa_node.TestNodeRemoveAll()
473

    
474

    
475
if __name__ == "__main__":
476
  Main()