rapi-workload.py 4.59 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 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 150 151 152 153 154 155 156 157 158
#!/usr/bin/python -u
#

# Copyright (C) 2013 Google Inc.
#
# 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.


"""Script for providing a large amount of RAPI calls to Ganeti.

"""

# pylint: disable=C0103
# due to invalid name


import sys

from ganeti.rapi.client import GanetiApiError

import qa_config
import qa_node
import qa_rapi


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


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

  """
  return None


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

  @type fn arbitrary function
  @param fn The function to invoke later.
  @type name string
  @param name The name of the function, for debugging purposes.
  @rtype function

  """
  def decoratedFn(*args, **kwargs):
    result = None
    try:
      print "Using method %s" % name
      result = fn(*args, **kwargs)
    except GanetiApiError as e:
      print "RAPI error while performing function %s : %s" % \
            (name, str(e))
    return result

  return decoratedFn


RAPI_USERNAME = "ganeti-qa"


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

  """
  def __init__(self):
    self._client = qa_rapi.Setup(RAPI_USERNAME,
                                 qa_rapi.LookupRapiSecret(RAPI_USERNAME))

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

    """
    # Assuming that this method exposes no public methods of its own,
    # and that any private methods are named according to the style
    # guide, this will stop infinite loops in attribute fetches.
    if attr.startswith("_"):
      return self.__getattribute__(attr)

    try:
      return InvokerCreator(self._client.__getattribute__(attr), attr)
    except AttributeError:
      print "Missing method %s; supplying mock method" % attr
      return MockMethod


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

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

  """
  print client.GetVersion()


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


def Main():
  if len(sys.argv) < 2:
    Usage()

  qa_config.Load(sys.argv[1])

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

  client = GanetiRapiClientWrapper()

  Workload(client)

  qa_node.TestNodeRemoveAll()


if __name__ == "__main__":
  Main()