Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.3 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
from ganeti import runtime
38

    
39
import testutils
40

    
41

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

    
48

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

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

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

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

64
  """
65

    
66
  REMOVE = object()
67

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

    
72
    try:
73
      runtime.InitArchInfo()
74
    except errors.ProgrammerError:
75
      # during tests, the arch info can be initialized multiple times
76
      pass
77

    
78
    self.ResetMocks()
79

    
80
  def _StopPatchers(self):
81
    if self._iallocator_patcher is not None:
82
      self._iallocator_patcher.stop()
83
      self._iallocator_patcher = None
84

    
85
  def tearDown(self):
86
    super(CmdlibTestCase, self).tearDown()
87

    
88
    self._StopPatchers()
89

    
90
  def _GetTestModule(self):
91
    module = inspect.getsourcefile(self.__class__).split("/")[-1]
92
    suffix = "_unittest.py"
93
    assert module.endswith(suffix), "Naming convention for cmdlib test" \
94
                                    " modules is: <module>%s (found '%s')"\
95
                                    % (suffix, module)
96
    return module[:-len(suffix)]
97

    
98
  def ResetMocks(self):
99
    """Resets all mocks back to their initial state.
100

101
    This is useful if you want to execute more than one opcode in a single
102
    test.
103

104
    """
105
    self.cfg = ConfigMock()
106
    self.glm = LockManagerMock()
107
    self.rpc = CreateRpcRunnerMock()
108

    
109
    self._StopPatchers()
110
    try:
111
      self._iallocator_patcher = patchIAllocator(self._GetTestModule())
112
      self.iallocator_cls = self._iallocator_patcher.start()
113
    except ImportError:
114
      # this test module does not use iallocator, no patching performed
115
      self._iallocator_patcher = None
116

    
117
    ctx = GanetiContextMock(self.cfg, self.glm, self.rpc)
118
    self.mcpu = ProcessorMock(ctx)
119

    
120
  def ExecOpCode(self, opcode):
121
    """Executes the given opcode.
122

123
    @param opcode: the opcode to execute
124
    @return: the result of the LU's C{Exec} method
125

126
    """
127
    self.glm.AddLocksFromConfig(self.cfg)
128

    
129
    return self.mcpu.ExecOpCodeAndRecordOutput(opcode)
130

    
131
  def ExecOpCodeExpectException(self, opcode,
132
                                expected_exception,
133
                                expected_regex=None):
134
    """Executes the given opcode and expects an exception.
135

136
    @param opcode: @see L{ExecOpCode}
137
    @type expected_exception: class
138
    @param expected_exception: the exception which must be raised
139
    @type expected_regex: string
140
    @param expected_regex: if not C{None}, a regular expression which must be
141
          present in the string representation of the exception
142

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

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

164
    @see L{ExecOpCodeExpectException}
165

166
    """
167
    self.ExecOpCodeExpectException(opcode, errors.OpPrereqError, expected_regex)
168

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

172
    @see L{ExecOpCodeExpectException}
173

174
    """
175
    self.ExecOpCodeExpectException(opcode, errors.OpExecError, expected_regex)
176

    
177
  def assertLogContainsMessage(self, expected_msg):
178
    """Shortcut for L{ProcessorMock.assertLogContainsMessage}
179

180
    """
181
    self.mcpu.assertLogContainsMessage(expected_msg)
182

    
183
  def assertLogContainsRegex(self, expected_regex):
184
    """Shortcut for L{ProcessorMock.assertLogContainsRegex}
185

186
    """
187
    self.mcpu.assertLogContainsRegex(expected_regex)
188

    
189
  def CopyOpCode(self, opcode, **kwargs):
190
    """Creates a copy of the given opcode and applies modifications to it
191

192
    @type opcode: opcode.OpCode
193
    @param opcode: the opcode to copy
194
    @type kwargs: dict
195
    @param kwargs: dictionary of fields to overwrite in the copy. The special
196
          value L{REMOVE} can be used to remove fields from the copy.
197
    @return: a copy of the given opcode
198

199
    """
200
    state = opcode.__getstate__()
201

    
202
    for key, value in kwargs.items():
203
      if value == self.REMOVE:
204
        del state[key]
205
      else:
206
        state[key] = value
207

    
208
    return opcodes.OpCode.LoadOpCode(state)