Statistics
| Branch: | Tag: | Revision:

root / test / py / cmdlib / testsupport / cmdlib_testcase.py @ e969a81f

History | View | Annotate | Download (6.2 kB)

1
#
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
"""Main module of the cmdlib test framework"""
23

    
24

    
25
import inspect
26
import re
27
import traceback
28

    
29
from cmdlib.testsupport.config_mock import ConfigMock
30
from cmdlib.testsupport.iallocator_mock import patchIAllocator
31
from cmdlib.testsupport.lock_manager_mock import LockManagerMock
32
from cmdlib.testsupport.processor_mock import ProcessorMock
33
from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock
34

    
35
from ganeti import errors
36
from ganeti import opcodes
37

    
38
import testutils
39

    
40

    
41
class GanetiContextMock(object):
42
  def __init__(self, cfg, glm, rpc):
43
    self.cfg = cfg
44
    self.glm = glm
45
    self.rpc = rpc
46

    
47

    
48
# pylint: disable=R0904
49
class CmdlibTestCase(testutils.GanetiTestCase):
50
  """Base class for cmdlib tests.
51

52
  This class sets up a mocked environment for the execution of
53
  L{ganeti.cmdlib.base.LogicalUnit} subclasses.
54

55
  The environment can be customized via the following fields:
56

57
    * C{cfg}: @see L{ConfigMock}
58
    * C{glm}: @see L{LockManagerMock}
59
    * C{rpc}: @see L{CreateRpcRunnerMock}
60
    * C{iallocator_cls}: @see L{patchIAllocator}
61
    * C{mcpu}: @see L{ProcessorMock}
62

63
  """
64

    
65
  REMOVE = object()
66

    
67
  def setUp(self):
68
    super(CmdlibTestCase, self).setUp()
69
    self._iallocator_patcher = None
70

    
71
    self.ResetMocks()
72

    
73
  def _StopPatchers(self):
74
    if self._iallocator_patcher is not None:
75
      self._iallocator_patcher.stop()
76
      self._iallocator_patcher = None
77

    
78
  def tearDown(self):
79
    super(CmdlibTestCase, self).tearDown()
80

    
81
    self._StopPatchers()
82

    
83
  def _GetTestModule(self):
84
    module = inspect.getsourcefile(self.__class__).split("/")[-1]
85
    suffix = "_unittest.py"
86
    assert module.endswith(suffix), "Naming convention for cmdlib test" \
87
                                    " modules is: <module>%s (found '%s')"\
88
                                    % (suffix, module)
89
    return module[:-len(suffix)]
90

    
91
  def ResetMocks(self):
92
    """Resets all mocks back to their initial state.
93

94
    This is useful if you want to execute more than one opcode in a single
95
    test.
96

97
    """
98
    self.cfg = ConfigMock()
99
    self.glm = LockManagerMock()
100
    self.rpc = CreateRpcRunnerMock()
101

    
102
    self._StopPatchers()
103
    try:
104
      self._iallocator_patcher = patchIAllocator(self._GetTestModule())
105
      self.iallocator_cls = self._iallocator_patcher.start()
106
    except ImportError:
107
      # this test module does not use iallocator, no patching performed
108
      self._iallocator_patcher = None
109

    
110
    ctx = GanetiContextMock(self.cfg, self.glm, self.rpc)
111
    self.mcpu = ProcessorMock(ctx)
112

    
113
  def ExecOpCode(self, opcode):
114
    """Executes the given opcode.
115

116
    @param opcode: the opcode to execute
117
    @return: the result of the LU's C{Exec} method
118

119
    """
120
    self.glm.AddLocksFromConfig(self.cfg)
121

    
122
    return self.mcpu.ExecOpCodeAndRecordOutput(opcode)
123

    
124
  def ExecOpCodeExpectException(self, opcode,
125
                                expected_exception,
126
                                expected_regex=None):
127
    """Executes the given opcode and expects an exception.
128

129
    @param opcode: @see L{ExecOpCode}
130
    @type expected_exception: class
131
    @param expected_exception: the exception which must be raised
132
    @type expected_regex: string
133
    @param expected_regex: if not C{None}, a regular expression which must be
134
          present in the string representation of the exception
135

136
    """
137
    try:
138
      self.ExecOpCode(opcode)
139
    except expected_exception, e:
140
      if expected_regex is not None:
141
        assert re.search(expected_regex, str(e)) is not None, \
142
                "Caught exception '%s' did not match '%s'" % \
143
                  (str(e), expected_regex)
144
    except Exception, e:
145
      tb = traceback.format_exc()
146
      raise AssertionError("%s\n(See original exception above)\n"
147
                           "Expected exception '%s' was not raised,"
148
                           " got '%s' of class '%s' instead." %
149
                           (tb, expected_exception, e, e.__class__))
150
    else:
151
      raise AssertionError("Expected exception '%s' was not raised" %
152
                           expected_exception)
153

    
154
  def ExecOpCodeExpectOpPrereqError(self, opcode, expected_regex=None):
155
    """Executes the given opcode and expects a L{errors.OpPrereqError}
156

157
    @see L{ExecOpCodeExpectException}
158

159
    """
160
    self.ExecOpCodeExpectException(opcode, errors.OpPrereqError, expected_regex)
161

    
162
  def ExecOpCodeExpectOpExecError(self, opcode, expected_regex=None):
163
    """Executes the given opcode and expects a L{errors.OpExecError}
164

165
    @see L{ExecOpCodeExpectException}
166

167
    """
168
    self.ExecOpCodeExpectException(opcode, errors.OpExecError, expected_regex)
169

    
170
  def assertLogContainsMessage(self, expected_msg):
171
    """Shortcut for L{ProcessorMock.assertLogContainsMessage}
172

173
    """
174
    self.mcpu.assertLogContainsMessage(expected_msg)
175

    
176
  def assertLogContainsRegex(self, expected_regex):
177
    """Shortcut for L{ProcessorMock.assertLogContainsRegex}
178

179
    """
180
    self.mcpu.assertLogContainsRegex(expected_regex)
181

    
182
  def CopyOpCode(self, opcode, **kwargs):
183
    """Creates a copy of the given opcode and applies modifications to it
184

185
    @type opcode: opcode.OpCode
186
    @param opcode: the opcode to copy
187
    @type kwargs: dict
188
    @param kwargs: dictionary of fields to overwrite in the copy. The special
189
          value L{REMOVE} can be used to remove fields from the copy.
190
    @return: a copy of the given opcode
191

192
    """
193
    state = opcode.__getstate__()
194

    
195
    for key, value in kwargs.items():
196
      if value == self.REMOVE:
197
        del state[key]
198
      else:
199
        state[key] = value
200

    
201
    return opcodes.OpCode.LoadOpCode(state)