Statistics
| Branch: | Tag: | Revision:

root / qa / rapi-workload.py @ b87948f5

History | View | Annotate | Download (6.1 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 Workload(client):
160
  """ The actual RAPI workload used for tests.
161

162
  @type client C{GanetiRapiClientWrapper}
163
  @param client A wrapped RAPI client.
164

165
  """
166

    
167
  # First just the simple information retrievals
168
  client.GetVersion()
169
  client.GetFeatures()
170
  client.GetOperatingSystems()
171
  client.GetInfo()
172
  client.GetClusterTags()
173
  client.GetInstances()
174
  client.GetInstances(bulk=True)
175
  client.GetJobs()
176
  client.GetJobs(bulk=True)
177
  client.GetNodes()
178
  client.GetNodes(bulk=True)
179
  client.GetNetworks()
180
  client.GetNetworks(bulk=True)
181
  client.GetGroups()
182
  client.GetGroups(bulk=True)
183

    
184
  Finish(client, client.RedistributeConfig)
185

    
186

    
187
def Usage():
188
  sys.stderr.write("Usage:\n\trapi-workload.py qa-config-file")
189

    
190

    
191
def Main():
192
  if len(sys.argv) < 2:
193
    Usage()
194

    
195
  qa_config.Load(sys.argv[1])
196

    
197
  # Only the master will be present after a fresh QA cluster setup, so we have
198
  # to invoke this to get all the other nodes.
199
  qa_node.TestNodeAddAll()
200

    
201
  client = GanetiRapiClientWrapper()
202

    
203
  Workload(client)
204

    
205
  qa_node.TestNodeRemoveAll()
206

    
207

    
208
if __name__ == "__main__":
209
  Main()