root / test / py / cmdlib / testsupport / cmdlib_testcase.py @ e969a81f
History | View | Annotate | Download (6.2 kB)
1 | 3efa7659 | Thomas Thrainer | #
|
---|---|---|---|
2 | 3efa7659 | Thomas Thrainer | #
|
3 | 3efa7659 | Thomas Thrainer | |
4 | 3efa7659 | Thomas Thrainer | # Copyright (C) 2013 Google Inc.
|
5 | 3efa7659 | Thomas Thrainer | #
|
6 | 3efa7659 | Thomas Thrainer | # This program is free software; you can redistribute it and/or modify
|
7 | 3efa7659 | Thomas Thrainer | # it under the terms of the GNU General Public License as published by
|
8 | 3efa7659 | Thomas Thrainer | # the Free Software Foundation; either version 2 of the License, or
|
9 | 3efa7659 | Thomas Thrainer | # (at your option) any later version.
|
10 | 3efa7659 | Thomas Thrainer | #
|
11 | 3efa7659 | Thomas Thrainer | # This program is distributed in the hope that it will be useful, but
|
12 | 3efa7659 | Thomas Thrainer | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | 3efa7659 | Thomas Thrainer | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | 3efa7659 | Thomas Thrainer | # General Public License for more details.
|
15 | 3efa7659 | Thomas Thrainer | #
|
16 | 3efa7659 | Thomas Thrainer | # You should have received a copy of the GNU General Public License
|
17 | 3efa7659 | Thomas Thrainer | # along with this program; if not, write to the Free Software
|
18 | 3efa7659 | Thomas Thrainer | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | 3efa7659 | Thomas Thrainer | # 02110-1301, USA.
|
20 | 3efa7659 | Thomas Thrainer | |
21 | bd39b6bb | Thomas Thrainer | |
22 | bd39b6bb | Thomas Thrainer | """Main module of the cmdlib test framework"""
|
23 | bd39b6bb | Thomas Thrainer | |
24 | bd39b6bb | Thomas Thrainer | |
25 | e969a81f | Thomas Thrainer | import inspect |
26 | e969a81f | Thomas Thrainer | import re |
27 | e969a81f | Thomas Thrainer | import traceback |
28 | e969a81f | Thomas Thrainer | |
29 | bd39b6bb | Thomas Thrainer | from cmdlib.testsupport.config_mock import ConfigMock |
30 | e969a81f | Thomas Thrainer | from cmdlib.testsupport.iallocator_mock import patchIAllocator |
31 | bd39b6bb | Thomas Thrainer | from cmdlib.testsupport.lock_manager_mock import LockManagerMock |
32 | bd39b6bb | Thomas Thrainer | from cmdlib.testsupport.processor_mock import ProcessorMock |
33 | bd39b6bb | Thomas Thrainer | from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock |
34 | 3efa7659 | Thomas Thrainer | |
35 | e969a81f | Thomas Thrainer | from ganeti import errors |
36 | e969a81f | Thomas Thrainer | from ganeti import opcodes |
37 | e969a81f | Thomas Thrainer | |
38 | 3efa7659 | Thomas Thrainer | import testutils |
39 | 3efa7659 | Thomas Thrainer | |
40 | 3efa7659 | Thomas Thrainer | |
41 | 3efa7659 | Thomas Thrainer | class GanetiContextMock(object): |
42 | 3efa7659 | Thomas Thrainer | def __init__(self, cfg, glm, rpc): |
43 | 3efa7659 | Thomas Thrainer | self.cfg = cfg
|
44 | 3efa7659 | Thomas Thrainer | self.glm = glm
|
45 | 3efa7659 | Thomas Thrainer | self.rpc = rpc
|
46 | 3efa7659 | Thomas Thrainer | |
47 | 3efa7659 | Thomas Thrainer | |
48 | bd39b6bb | Thomas Thrainer | # pylint: disable=R0904
|
49 | 3efa7659 | Thomas Thrainer | class CmdlibTestCase(testutils.GanetiTestCase): |
50 | 3efa7659 | Thomas Thrainer | """Base class for cmdlib tests.
|
51 | 3efa7659 | Thomas Thrainer |
|
52 | 3efa7659 | Thomas Thrainer | This class sets up a mocked environment for the execution of
|
53 | 3efa7659 | Thomas Thrainer | L{ganeti.cmdlib.base.LogicalUnit} subclasses.
|
54 | 3efa7659 | Thomas Thrainer |
|
55 | 3efa7659 | Thomas Thrainer | The environment can be customized via the following fields:
|
56 | 3efa7659 | Thomas Thrainer |
|
57 | 3efa7659 | Thomas Thrainer | * C{cfg}: @see L{ConfigMock}
|
58 | 3efa7659 | Thomas Thrainer | * C{glm}: @see L{LockManagerMock}
|
59 | 3efa7659 | Thomas Thrainer | * C{rpc}: @see L{CreateRpcRunnerMock}
|
60 | e969a81f | Thomas Thrainer | * C{iallocator_cls}: @see L{patchIAllocator}
|
61 | 3efa7659 | Thomas Thrainer | * C{mcpu}: @see L{ProcessorMock}
|
62 | 3efa7659 | Thomas Thrainer |
|
63 | 3efa7659 | Thomas Thrainer | """
|
64 | e969a81f | Thomas Thrainer | |
65 | e969a81f | Thomas Thrainer | REMOVE = object()
|
66 | e969a81f | Thomas Thrainer | |
67 | 3efa7659 | Thomas Thrainer | def setUp(self): |
68 | 3efa7659 | Thomas Thrainer | super(CmdlibTestCase, self).setUp() |
69 | e969a81f | Thomas Thrainer | self._iallocator_patcher = None |
70 | e969a81f | Thomas Thrainer | |
71 | e969a81f | Thomas Thrainer | self.ResetMocks()
|
72 | e969a81f | Thomas Thrainer | |
73 | e969a81f | Thomas Thrainer | def _StopPatchers(self): |
74 | e969a81f | Thomas Thrainer | if self._iallocator_patcher is not None: |
75 | e969a81f | Thomas Thrainer | self._iallocator_patcher.stop()
|
76 | e969a81f | Thomas Thrainer | self._iallocator_patcher = None |
77 | e969a81f | Thomas Thrainer | |
78 | e969a81f | Thomas Thrainer | def tearDown(self): |
79 | e969a81f | Thomas Thrainer | super(CmdlibTestCase, self).tearDown() |
80 | 3efa7659 | Thomas Thrainer | |
81 | e969a81f | Thomas Thrainer | self._StopPatchers()
|
82 | e969a81f | Thomas Thrainer | |
83 | e969a81f | Thomas Thrainer | def _GetTestModule(self): |
84 | e969a81f | Thomas Thrainer | module = inspect.getsourcefile(self.__class__).split("/")[-1] |
85 | e969a81f | Thomas Thrainer | suffix = "_unittest.py"
|
86 | e969a81f | Thomas Thrainer | assert module.endswith(suffix), "Naming convention for cmdlib test" \ |
87 | e969a81f | Thomas Thrainer | " modules is: <module>%s (found '%s')"\
|
88 | e969a81f | Thomas Thrainer | % (suffix, module) |
89 | e969a81f | Thomas Thrainer | return module[:-len(suffix)] |
90 | e969a81f | Thomas Thrainer | |
91 | e969a81f | Thomas Thrainer | def ResetMocks(self): |
92 | e969a81f | Thomas Thrainer | """Resets all mocks back to their initial state.
|
93 | e969a81f | Thomas Thrainer |
|
94 | e969a81f | Thomas Thrainer | This is useful if you want to execute more than one opcode in a single
|
95 | e969a81f | Thomas Thrainer | test.
|
96 | e969a81f | Thomas Thrainer |
|
97 | e969a81f | Thomas Thrainer | """
|
98 | 3efa7659 | Thomas Thrainer | self.cfg = ConfigMock()
|
99 | 3efa7659 | Thomas Thrainer | self.glm = LockManagerMock()
|
100 | 3efa7659 | Thomas Thrainer | self.rpc = CreateRpcRunnerMock()
|
101 | e969a81f | Thomas Thrainer | |
102 | e969a81f | Thomas Thrainer | self._StopPatchers()
|
103 | e969a81f | Thomas Thrainer | try:
|
104 | e969a81f | Thomas Thrainer | self._iallocator_patcher = patchIAllocator(self._GetTestModule()) |
105 | e969a81f | Thomas Thrainer | self.iallocator_cls = self._iallocator_patcher.start() |
106 | e969a81f | Thomas Thrainer | except ImportError: |
107 | e969a81f | Thomas Thrainer | # this test module does not use iallocator, no patching performed
|
108 | e969a81f | Thomas Thrainer | self._iallocator_patcher = None |
109 | e969a81f | Thomas Thrainer | |
110 | 3efa7659 | Thomas Thrainer | ctx = GanetiContextMock(self.cfg, self.glm, self.rpc) |
111 | 3efa7659 | Thomas Thrainer | self.mcpu = ProcessorMock(ctx)
|
112 | 3efa7659 | Thomas Thrainer | |
113 | 3efa7659 | Thomas Thrainer | def ExecOpCode(self, opcode): |
114 | 3efa7659 | Thomas Thrainer | """Executes the given opcode.
|
115 | 3efa7659 | Thomas Thrainer |
|
116 | 3efa7659 | Thomas Thrainer | @param opcode: the opcode to execute
|
117 | 3efa7659 | Thomas Thrainer | @return: the result of the LU's C{Exec} method
|
118 | e969a81f | Thomas Thrainer |
|
119 | 3efa7659 | Thomas Thrainer | """
|
120 | 3efa7659 | Thomas Thrainer | self.glm.AddLocksFromConfig(self.cfg) |
121 | 3efa7659 | Thomas Thrainer | |
122 | 3efa7659 | Thomas Thrainer | return self.mcpu.ExecOpCodeAndRecordOutput(opcode) |
123 | 3efa7659 | Thomas Thrainer | |
124 | e969a81f | Thomas Thrainer | def ExecOpCodeExpectException(self, opcode, |
125 | e969a81f | Thomas Thrainer | expected_exception, |
126 | e969a81f | Thomas Thrainer | expected_regex=None):
|
127 | e969a81f | Thomas Thrainer | """Executes the given opcode and expects an exception.
|
128 | e969a81f | Thomas Thrainer |
|
129 | e969a81f | Thomas Thrainer | @param opcode: @see L{ExecOpCode}
|
130 | e969a81f | Thomas Thrainer | @type expected_exception: class
|
131 | e969a81f | Thomas Thrainer | @param expected_exception: the exception which must be raised
|
132 | e969a81f | Thomas Thrainer | @type expected_regex: string
|
133 | e969a81f | Thomas Thrainer | @param expected_regex: if not C{None}, a regular expression which must be
|
134 | e969a81f | Thomas Thrainer | present in the string representation of the exception
|
135 | e969a81f | Thomas Thrainer |
|
136 | e969a81f | Thomas Thrainer | """
|
137 | e969a81f | Thomas Thrainer | try:
|
138 | e969a81f | Thomas Thrainer | self.ExecOpCode(opcode)
|
139 | e969a81f | Thomas Thrainer | except expected_exception, e:
|
140 | e969a81f | Thomas Thrainer | if expected_regex is not None: |
141 | e969a81f | Thomas Thrainer | assert re.search(expected_regex, str(e)) is not None, \ |
142 | e969a81f | Thomas Thrainer | "Caught exception '%s' did not match '%s'" % \
|
143 | e969a81f | Thomas Thrainer | (str(e), expected_regex)
|
144 | e969a81f | Thomas Thrainer | except Exception, e: |
145 | e969a81f | Thomas Thrainer | tb = traceback.format_exc() |
146 | e969a81f | Thomas Thrainer | raise AssertionError("%s\n(See original exception above)\n" |
147 | e969a81f | Thomas Thrainer | "Expected exception '%s' was not raised,"
|
148 | e969a81f | Thomas Thrainer | " got '%s' of class '%s' instead." %
|
149 | e969a81f | Thomas Thrainer | (tb, expected_exception, e, e.__class__)) |
150 | e969a81f | Thomas Thrainer | else:
|
151 | e969a81f | Thomas Thrainer | raise AssertionError("Expected exception '%s' was not raised" % |
152 | e969a81f | Thomas Thrainer | expected_exception) |
153 | e969a81f | Thomas Thrainer | |
154 | e969a81f | Thomas Thrainer | def ExecOpCodeExpectOpPrereqError(self, opcode, expected_regex=None): |
155 | e969a81f | Thomas Thrainer | """Executes the given opcode and expects a L{errors.OpPrereqError}
|
156 | e969a81f | Thomas Thrainer |
|
157 | e969a81f | Thomas Thrainer | @see L{ExecOpCodeExpectException}
|
158 | e969a81f | Thomas Thrainer |
|
159 | e969a81f | Thomas Thrainer | """
|
160 | e969a81f | Thomas Thrainer | self.ExecOpCodeExpectException(opcode, errors.OpPrereqError, expected_regex)
|
161 | e969a81f | Thomas Thrainer | |
162 | e969a81f | Thomas Thrainer | def ExecOpCodeExpectOpExecError(self, opcode, expected_regex=None): |
163 | e969a81f | Thomas Thrainer | """Executes the given opcode and expects a L{errors.OpExecError}
|
164 | e969a81f | Thomas Thrainer |
|
165 | e969a81f | Thomas Thrainer | @see L{ExecOpCodeExpectException}
|
166 | e969a81f | Thomas Thrainer |
|
167 | e969a81f | Thomas Thrainer | """
|
168 | e969a81f | Thomas Thrainer | self.ExecOpCodeExpectException(opcode, errors.OpExecError, expected_regex)
|
169 | e969a81f | Thomas Thrainer | |
170 | 3efa7659 | Thomas Thrainer | def assertLogContainsMessage(self, expected_msg): |
171 | 3efa7659 | Thomas Thrainer | """Shortcut for L{ProcessorMock.assertLogContainsMessage}
|
172 | 3efa7659 | Thomas Thrainer |
|
173 | 3efa7659 | Thomas Thrainer | """
|
174 | 3efa7659 | Thomas Thrainer | self.mcpu.assertLogContainsMessage(expected_msg)
|
175 | 3efa7659 | Thomas Thrainer | |
176 | 3efa7659 | Thomas Thrainer | def assertLogContainsRegex(self, expected_regex): |
177 | 3efa7659 | Thomas Thrainer | """Shortcut for L{ProcessorMock.assertLogContainsRegex}
|
178 | 3efa7659 | Thomas Thrainer |
|
179 | 3efa7659 | Thomas Thrainer | """
|
180 | 3efa7659 | Thomas Thrainer | self.mcpu.assertLogContainsRegex(expected_regex)
|
181 | bd39b6bb | Thomas Thrainer | |
182 | e969a81f | Thomas Thrainer | def CopyOpCode(self, opcode, **kwargs): |
183 | e969a81f | Thomas Thrainer | """Creates a copy of the given opcode and applies modifications to it
|
184 | e969a81f | Thomas Thrainer |
|
185 | e969a81f | Thomas Thrainer | @type opcode: opcode.OpCode
|
186 | e969a81f | Thomas Thrainer | @param opcode: the opcode to copy
|
187 | e969a81f | Thomas Thrainer | @type kwargs: dict
|
188 | e969a81f | Thomas Thrainer | @param kwargs: dictionary of fields to overwrite in the copy. The special
|
189 | e969a81f | Thomas Thrainer | value L{REMOVE} can be used to remove fields from the copy.
|
190 | e969a81f | Thomas Thrainer | @return: a copy of the given opcode
|
191 | e969a81f | Thomas Thrainer |
|
192 | e969a81f | Thomas Thrainer | """
|
193 | e969a81f | Thomas Thrainer | state = opcode.__getstate__() |
194 | e969a81f | Thomas Thrainer | |
195 | e969a81f | Thomas Thrainer | for key, value in kwargs.items(): |
196 | e969a81f | Thomas Thrainer | if value == self.REMOVE: |
197 | e969a81f | Thomas Thrainer | del state[key]
|
198 | e969a81f | Thomas Thrainer | else:
|
199 | e969a81f | Thomas Thrainer | state[key] = value |
200 | e969a81f | Thomas Thrainer | |
201 | e969a81f | Thomas Thrainer | return opcodes.OpCode.LoadOpCode(state) |