Statistics
| Branch: | Tag: | Revision:

root / qa / rapi-workload.py @ a9e3e04d

History | View | Annotate | Download (11.8 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
from ganeti.rapi.client import GanetiApiError
33

    
34
import qa_config
35
import qa_node
36
import qa_rapi
37

    
38

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

    
63

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

67
  """
68
  return None
69

    
70

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

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

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

    
94
  return decoratedFn
95

    
96

    
97
RAPI_USERNAME = "ganeti-qa"
98

    
99

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

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

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

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

    
124

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

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

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

    
148
  success = client.WaitForJobCompletion(possible_job_id)
149

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

    
158

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

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

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

174
  """
175
  get_fn(*args)
176

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

    
181
  get_fn(*args)
182

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

    
186
  get_fn(*args)
187

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

    
190
  get_fn(*args)
191

    
192

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

197
  @type client C{GanetiRapiClientWrapper}
198

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

    
216

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

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

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

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

    
231

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

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

248
  """
249

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

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

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

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

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

    
272
  client.GetInstance(instance_name)
273

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
317
  Finish(client, client.RecreateInstanceDisks,
318
         instance_name, [0], [node_one])
319

    
320
  Finish(client, client.StartupInstance,
321
         instance_name, dry_run=False, no_remember=False)
322

    
323
  client.GetInstanceConsole(instance_name)
324

    
325
  Finish(client, client.ReinstallInstance,
326
         instance_name, os=None, no_startup=False, osparams={})
327

    
328
  Finish(client, client.DeleteInstance, instance_name, dry_run=True)
329

    
330
  Finish(client, client.DeleteInstance, instance_name)
331

    
332

    
333
def Workload(client):
334
  """ The actual RAPI workload used for tests.
335

336
  @type client C{GanetiRapiClientWrapper}
337
  @param client A wrapped RAPI client.
338

339
  """
340

    
341
  # First just the simple information retrievals
342
  TestGetters(client)
343

    
344
  # Then the only remaining function which is parameter-free
345
  Finish(client, client.RedistributeConfig)
346

    
347
  TestTags(client, client.GetClusterTags, client.AddClusterTags,
348
           client.DeleteClusterTags)
349

    
350
  # Generously assume the master is present
351
  node = qa_config.AcquireNode()
352
  TestTags(client, client.GetNodeTags, client.AddNodeTags,
353
           client.DeleteNodeTags, node.primary)
354
  node.Release()
355

    
356
  # Instance tests
357

    
358
  # First remove all instances the QA might have created
359
  RemoveAllInstances(client)
360

    
361
  nodes = qa_config.AcquireManyNodes(2)
362
  instance_one = qa_config.AcquireInstance()
363
  instance_two = qa_config.AcquireInstance()
364
  TestSingleInstance(client, instance_one.name, instance_two.name,
365
                     nodes[0].primary, nodes[1].primary)
366
  instance_two.Release()
367
  instance_one.Release()
368
  qa_config.ReleaseManyNodes(nodes)
369

    
370

    
371
def Usage():
372
  sys.stderr.write("Usage:\n\trapi-workload.py qa-config-file")
373

    
374

    
375
def Main():
376
  if len(sys.argv) < 2:
377
    Usage()
378

    
379
  qa_config.Load(sys.argv[1])
380

    
381
  # Only the master will be present after a fresh QA cluster setup, so we have
382
  # to invoke this to get all the other nodes.
383
  qa_node.TestNodeAddAll()
384

    
385
  client = GanetiRapiClientWrapper()
386

    
387
  Workload(client)
388

    
389
  qa_node.TestNodeRemoveAll()
390

    
391

    
392
if __name__ == "__main__":
393
  Main()