Revision 3efa7659
b/Makefile.am | ||
---|---|---|
137 | 137 |
test/data/ovfdata \ |
138 | 138 |
test/data/ovfdata/other \ |
139 | 139 |
test/py \ |
140 |
test/py/cmdlib \ |
|
141 |
test/py/cmdlib/testsupport \ |
|
140 | 142 |
tools |
141 | 143 |
|
142 | 144 |
ALL_APIDOC_HS_DIRS = \ |
... | ... | |
1026 | 1028 |
doc/examples/gnt-debug/README \ |
1027 | 1029 |
doc/examples/gnt-debug/delay0.json \ |
1028 | 1030 |
doc/examples/gnt-debug/delay50.json \ |
1029 |
test/py/lockperf.py \ |
|
1030 |
test/py/testutils.py \ |
|
1031 |
test/py/mocks.py \ |
|
1032 | 1031 |
$(dist_TESTS) \ |
1033 | 1032 |
$(TEST_FILES) \ |
1033 |
$(python_test_support) \ |
|
1034 | 1034 |
man/footer.rst \ |
1035 | 1035 |
$(manrst) \ |
1036 | 1036 |
$(maninput) \ |
... | ... | |
1237 | 1237 |
|
1238 | 1238 |
python_tests = \ |
1239 | 1239 |
doc/examples/rapi_testutils.py \ |
1240 |
test/py/cmdlib/test_unittest.py \ |
|
1240 | 1241 |
test/py/cfgupgrade_unittest.py \ |
1241 | 1242 |
test/py/docs_unittest.py \ |
1242 | 1243 |
test/py/ganeti.asyncnotifier_unittest.py \ |
... | ... | |
1322 | 1323 |
test/py/qa.qa_config_unittest.py \ |
1323 | 1324 |
test/py/tempfile_fork_unittest.py |
1324 | 1325 |
|
1326 |
python_test_support = \ |
|
1327 |
test/py/__init__.py \ |
|
1328 |
test/py/lockperf.py \ |
|
1329 |
test/py/testutils.py \ |
|
1330 |
test/py/mocks.py \ |
|
1331 |
test/py/cmdlib/__init__.py \ |
|
1332 |
test/py/cmdlib/testsupport/__init__.py \ |
|
1333 |
test/py/cmdlib/testsupport/cmdlib_testcase.py \ |
|
1334 |
test/py/cmdlib/testsupport/config_mock.py \ |
|
1335 |
test/py/cmdlib/testsupport/iallocator_mock.py \ |
|
1336 |
test/py/cmdlib/testsupport/lock_manager_mock.py \ |
|
1337 |
test/py/cmdlib/testsupport/processor_mock.py \ |
|
1338 |
test/py/cmdlib/testsupport/rpc_runner_mock.py |
|
1339 |
|
|
1325 | 1340 |
haskell_tests = test/hs/htest |
1326 | 1341 |
|
1327 | 1342 |
dist_TESTS = \ |
... | ... | |
1352 | 1367 |
|
1353 | 1368 |
# Environment for all tests |
1354 | 1369 |
PLAIN_TESTS_ENVIRONMENT = \ |
1355 |
PYTHONPATH=. \ |
|
1370 |
PYTHONPATH=.:./test/py \
|
|
1356 | 1371 |
TOP_SRCDIR=$(abs_top_srcdir) TOP_BUILDDIR=$(abs_top_builddir) \ |
1357 | 1372 |
PYTHON=$(PYTHON) FAKEROOT=$(FAKEROOT_PATH) \ |
1358 | 1373 |
$(RUN_IN_TEMPDIR) |
... | ... | |
1386 | 1401 |
|
1387 | 1402 |
if PY_UNIT |
1388 | 1403 |
all_python_code += $(python_tests) |
1404 |
all_python_code += $(python_test_support) |
|
1389 | 1405 |
endif |
1390 | 1406 |
|
1391 | 1407 |
srclink_files = \ |
b/lib/cmdlib/test.py | ||
---|---|---|
66 | 66 |
|
67 | 67 |
""" |
68 | 68 |
if self.op.on_master: |
69 |
if not utils.TestDelay(self.op.duration): |
|
69 |
if not utils.TestDelay(self.op.duration)[0]:
|
|
70 | 70 |
raise errors.OpExecError("Error during master delay test") |
71 | 71 |
if self.op.on_node_uuids: |
72 | 72 |
result = self.rpc.call_test_delay(self.op.on_node_uuids, self.op.duration) |
b/lib/config.py | ||
---|---|---|
167 | 167 |
return result |
168 | 168 |
|
169 | 169 |
|
170 |
class ConfigWriter: |
|
170 |
class ConfigWriter(object):
|
|
171 | 171 |
"""The interface to the cluster configuration. |
172 | 172 |
|
173 | 173 |
@ivar _temporary_lvs: reservation manager for temporary LVs |
b/lib/locking.py | ||
---|---|---|
1625 | 1625 |
NAL = "NAL" |
1626 | 1626 |
|
1627 | 1627 |
|
1628 |
class GanetiLockManager: |
|
1628 |
class GanetiLockManager(object):
|
|
1629 | 1629 |
"""The Ganeti Locking Library |
1630 | 1630 |
|
1631 | 1631 |
The purpose of this small library is to manage locking for ganeti clusters |
b/test/py/__init__.py | ||
---|---|---|
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 |
"""This module contains all python test code""" |
b/test/py/cmdlib/__init__.py | ||
---|---|---|
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 |
"""This module contains all cmdlib unit tests""" |
b/test/py/cmdlib/test_unittest.py | ||
---|---|---|
1 |
#!/usr/bin/python |
|
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 |
"""Tests for LUTest* |
|
23 |
|
|
24 |
""" |
|
25 |
|
|
26 |
|
|
27 |
from ganeti import constants |
|
28 |
from ganeti import errors |
|
29 |
from ganeti import opcodes |
|
30 |
|
|
31 |
from testsupport import * |
|
32 |
|
|
33 |
import testutils |
|
34 |
|
|
35 |
DELAY_DURATION = 0.01 |
|
36 |
|
|
37 |
|
|
38 |
class TestLUTestDelay(CmdlibTestCase): |
|
39 |
def testRepeatedInvocation(self): |
|
40 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
41 |
repeat=3) |
|
42 |
self.ExecOpCode(op) |
|
43 |
|
|
44 |
self.assertLogContainsMessage(" - INFO: Test delay iteration 0/2") |
|
45 |
self.mcpu.assertLogContainsEntry(constants.ELOG_MESSAGE, |
|
46 |
" - INFO: Test delay iteration 1/2") |
|
47 |
self.assertLogContainsRegex("2/2$") |
|
48 |
|
|
49 |
def testInvalidDuration(self): |
|
50 |
op = opcodes.OpTestDelay(duration=-1) |
|
51 |
|
|
52 |
self.assertRaises(errors.OpExecError, self.ExecOpCode, op) |
|
53 |
|
|
54 |
def testOnNodeUuid(self): |
|
55 |
node_uuids = [self.cfg.GetMasterNode()] |
|
56 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
57 |
on_node_uuids=node_uuids) |
|
58 |
self.ExecOpCode(op) |
|
59 |
|
|
60 |
self.rpc.call_test_delay.assert_called_once_with(node_uuids, DELAY_DURATION) |
|
61 |
|
|
62 |
def testOnNodeName(self): |
|
63 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
64 |
on_nodes=[self.cfg.GetMasterNodeName()]) |
|
65 |
self.ExecOpCode(op) |
|
66 |
|
|
67 |
self.rpc.call_test_delay.assert_called_once_with([self.cfg.GetMasterNode()], |
|
68 |
DELAY_DURATION) |
|
69 |
|
|
70 |
def testSuccessfulRpc(self): |
|
71 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
72 |
on_nodes=[self.cfg.GetMasterNodeName()]) |
|
73 |
|
|
74 |
self.rpc.call_test_delay.return_value = \ |
|
75 |
RpcResultsBuilder(cfg=self.cfg) \ |
|
76 |
.AddSuccessfulNode(self.cfg.GetMasterNode()) \ |
|
77 |
.Build() |
|
78 |
|
|
79 |
self.ExecOpCode(op) |
|
80 |
|
|
81 |
self.rpc.call_test_delay.assert_called_once() |
|
82 |
|
|
83 |
def testFailingRpc(self): |
|
84 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
85 |
on_nodes=[self.cfg.GetMasterNodeName()]) |
|
86 |
|
|
87 |
self.rpc.call_test_delay.return_value = \ |
|
88 |
RpcResultsBuilder(cfg=self.cfg) \ |
|
89 |
.AddFailedNode(self.cfg.GetMasterNode()) \ |
|
90 |
.Build() |
|
91 |
|
|
92 |
self.assertRaises(errors.OpExecError, self.ExecOpCode, op) |
|
93 |
|
|
94 |
def testMultipleNodes(self): |
|
95 |
node1 = self.cfg.AddNewNode() |
|
96 |
node2 = self.cfg.AddNewNode() |
|
97 |
op = opcodes.OpTestDelay(duration=DELAY_DURATION, |
|
98 |
on_nodes=[node1.name, node2.name]) |
|
99 |
|
|
100 |
self.rpc.call_test_delay.return_value = \ |
|
101 |
RpcResultsBuilder(cfg=self.cfg) \ |
|
102 |
.AddSuccessfulNode(node1) \ |
|
103 |
.AddSuccessfulNode(node2) \ |
|
104 |
.Build() |
|
105 |
|
|
106 |
self.ExecOpCode(op) |
|
107 |
|
|
108 |
self.rpc.call_test_delay.assert_called_once_with([node1.uuid, node2.uuid], |
|
109 |
DELAY_DURATION) |
|
110 |
|
|
111 |
|
|
112 |
if __name__ == "__main__": |
|
113 |
testutils.GanetiTestProgram() |
b/test/py/cmdlib/testsupport/__init__.py | ||
---|---|---|
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 |
"""Support classes and functions for testing the cmdlib module. |
|
23 |
|
|
24 |
""" |
|
25 |
|
|
26 |
from cmdlib_testcase import CmdlibTestCase |
|
27 |
from config_mock import ConfigMock |
|
28 |
from iallocator_mock import CreateIAllocatorMock |
|
29 |
from lock_manager_mock import LockManagerMock |
|
30 |
from processor_mock import ProcessorMock |
|
31 |
from rpc_runner_mock import CreateRpcRunnerMock, RpcResultsBuilder |
|
32 |
|
|
33 |
__all__ = ["CmdlibTestCase", |
|
34 |
"ConfigMock", |
|
35 |
"CreateIAllocatorMock", |
|
36 |
"CreateRpcRunnerMock", |
|
37 |
"LockManagerMock", |
|
38 |
"ProcessorMock", |
|
39 |
"RpcResultsBuilder", |
|
40 |
] |
b/test/py/cmdlib/testsupport/cmdlib_testcase.py | ||
---|---|---|
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 |
from config_mock import * |
|
22 |
from iallocator_mock import * |
|
23 |
from lock_manager_mock import * |
|
24 |
from processor_mock import * |
|
25 |
from rpc_runner_mock import * |
|
26 |
|
|
27 |
import testutils |
|
28 |
|
|
29 |
|
|
30 |
class GanetiContextMock(object): |
|
31 |
def __init__(self, cfg, glm, rpc): |
|
32 |
self.cfg = cfg |
|
33 |
self.glm = glm |
|
34 |
self.rpc = rpc |
|
35 |
|
|
36 |
|
|
37 |
class CmdlibTestCase(testutils.GanetiTestCase): |
|
38 |
"""Base class for cmdlib tests. |
|
39 |
|
|
40 |
This class sets up a mocked environment for the execution of |
|
41 |
L{ganeti.cmdlib.base.LogicalUnit} subclasses. |
|
42 |
|
|
43 |
The environment can be customized via the following fields: |
|
44 |
|
|
45 |
* C{cfg}: @see L{ConfigMock} |
|
46 |
* C{glm}: @see L{LockManagerMock} |
|
47 |
* C{rpc}: @see L{CreateRpcRunnerMock} |
|
48 |
* C{iallocator}: @see L{CreateIAllocatorMock} |
|
49 |
* C{mcpu}: @see L{ProcessorMock} |
|
50 |
|
|
51 |
""" |
|
52 |
def setUp(self): |
|
53 |
super(CmdlibTestCase, self).setUp() |
|
54 |
|
|
55 |
self.cfg = ConfigMock() |
|
56 |
self.glm = LockManagerMock() |
|
57 |
self.rpc = CreateRpcRunnerMock() |
|
58 |
self.iallocator = CreateIAllocatorMock() |
|
59 |
ctx = GanetiContextMock(self.cfg, self.glm, self.rpc) |
|
60 |
self.mcpu = ProcessorMock(ctx) |
|
61 |
|
|
62 |
def tearDown(self): |
|
63 |
super(CmdlibTestCase, self).tearDown() |
|
64 |
|
|
65 |
def ExecOpCode(self, opcode): |
|
66 |
"""Executes the given opcode. |
|
67 |
|
|
68 |
@param opcode: the opcode to execute |
|
69 |
@return: the result of the LU's C{Exec} method |
|
70 |
""" |
|
71 |
self.glm.AddLocksFromConfig(self.cfg) |
|
72 |
|
|
73 |
return self.mcpu.ExecOpCodeAndRecordOutput(opcode) |
|
74 |
|
|
75 |
def assertLogContainsMessage(self, expected_msg): |
|
76 |
"""Shortcut for L{ProcessorMock.assertLogContainsMessage} |
|
77 |
|
|
78 |
""" |
|
79 |
self.mcpu.assertLogContainsMessage(expected_msg) |
|
80 |
|
|
81 |
def assertLogContainsRegex(self, expected_regex): |
|
82 |
"""Shortcut for L{ProcessorMock.assertLogContainsRegex} |
|
83 |
|
|
84 |
""" |
|
85 |
self.mcpu.assertLogContainsRegex(expected_regex) |
b/test/py/cmdlib/testsupport/config_mock.py | ||
---|---|---|
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 |
import uuid |
|
22 |
|
|
23 |
from ganeti import config |
|
24 |
from ganeti import constants |
|
25 |
from ganeti import objects |
|
26 |
|
|
27 |
import mocks |
|
28 |
|
|
29 |
|
|
30 |
def _StubGetEntResolver(): |
|
31 |
return mocks.FakeGetentResolver() |
|
32 |
|
|
33 |
|
|
34 |
class ConfigMock(config.ConfigWriter): |
|
35 |
"""A mocked cluster configuration with added methods for easy customization. |
|
36 |
|
|
37 |
""" |
|
38 |
|
|
39 |
def __init__(self): |
|
40 |
self._cur_group_id = 1 |
|
41 |
self._cur_node_id = 1 |
|
42 |
self._cur_inst_id = 1 |
|
43 |
|
|
44 |
super(ConfigMock, self).__init__(cfg_file="/dev/null", |
|
45 |
_getents=_StubGetEntResolver()) |
|
46 |
|
|
47 |
def _GetUuid(self): |
|
48 |
return str(uuid.uuid4()) |
|
49 |
|
|
50 |
def AddNewNodeGroup(self, |
|
51 |
uuid=None, |
|
52 |
name=None, |
|
53 |
ndparams=None, |
|
54 |
diskparams=None, |
|
55 |
ipolicy=None, |
|
56 |
hv_state_static=None, |
|
57 |
disk_state_static=None, |
|
58 |
alloc_policy=None, |
|
59 |
networks=[]): |
|
60 |
"""Add a new L{objects.NodeGroup} to the cluster configuration |
|
61 |
|
|
62 |
See L{objects.NodeGroup} for parameter documentation. |
|
63 |
|
|
64 |
@rtype: L{objects.NodeGroup} |
|
65 |
@return: the newly added node group |
|
66 |
|
|
67 |
""" |
|
68 |
group_id = self._cur_group_id |
|
69 |
self._cur_group_id += 1 |
|
70 |
|
|
71 |
if uuid is None: |
|
72 |
uuid = self._GetUuid() |
|
73 |
if name is None: |
|
74 |
name = "mock_group_%d" % group_id |
|
75 |
|
|
76 |
group = objects.NodeGroup(uuid=uuid, |
|
77 |
name=name, |
|
78 |
ndparams=ndparams, |
|
79 |
diskparams=diskparams, |
|
80 |
ipolicy=ipolicy, |
|
81 |
hv_state_static=hv_state_static, |
|
82 |
disk_state_static=disk_state_static, |
|
83 |
alloc_policy=alloc_policy, |
|
84 |
networks=networks, |
|
85 |
members=[]) |
|
86 |
|
|
87 |
self.AddNodeGroup(group, None) |
|
88 |
return group |
|
89 |
|
|
90 |
def AddNewNode(self, |
|
91 |
uuid=None, |
|
92 |
name=None, |
|
93 |
primary_ip=None, |
|
94 |
secondary_ip=None, |
|
95 |
master_candidate=True, |
|
96 |
offline=False, |
|
97 |
drained=False, |
|
98 |
group=None, |
|
99 |
master_capable=True, |
|
100 |
vm_capable=True, |
|
101 |
ndparams=None, |
|
102 |
powered=True, |
|
103 |
hv_state=None, |
|
104 |
hv_state_static=None, |
|
105 |
disk_state=None, |
|
106 |
disk_state_static=None): |
|
107 |
"""Add a new L{objects.Node} to the cluster configuration |
|
108 |
|
|
109 |
See L{objects.Node} for parameter documentation. |
|
110 |
|
|
111 |
@rtype: L{objects.Node} |
|
112 |
@return: the newly added node |
|
113 |
|
|
114 |
""" |
|
115 |
node_id = self._cur_node_id |
|
116 |
self._cur_node_id += 1 |
|
117 |
|
|
118 |
if uuid is None: |
|
119 |
uuid = self._GetUuid() |
|
120 |
if name is None: |
|
121 |
name = "mock_node_%d.example.com" % node_id |
|
122 |
if primary_ip is None: |
|
123 |
primary_ip = "192.168.0.%d" % node_id |
|
124 |
if secondary_ip is None: |
|
125 |
secondary_ip = "192.168.1.%d" % node_id |
|
126 |
if group is None: |
|
127 |
group = self._default_group.uuid |
|
128 |
if isinstance(group, objects.NodeGroup): |
|
129 |
group = group.uuid |
|
130 |
|
|
131 |
node = objects.Node(uuid=uuid, |
|
132 |
name=name, |
|
133 |
primary_ip=primary_ip, |
|
134 |
secondary_ip=secondary_ip, |
|
135 |
master_candidate=master_candidate, |
|
136 |
offline=offline, |
|
137 |
drained=drained, |
|
138 |
group=group, |
|
139 |
master_capable=master_capable, |
|
140 |
vm_capable=vm_capable, |
|
141 |
ndparams=ndparams, |
|
142 |
powered=powered, |
|
143 |
hv_state=hv_state, |
|
144 |
hv_state_static=hv_state_static, |
|
145 |
disk_state=disk_state, |
|
146 |
disk_state_static=disk_state_static) |
|
147 |
|
|
148 |
self.AddNode(node, None) |
|
149 |
return node |
|
150 |
|
|
151 |
def AddNewInstance(self, |
|
152 |
uuid=None, |
|
153 |
name=None, |
|
154 |
primary_node=None, |
|
155 |
os="mocked_os", |
|
156 |
hypervisor=constants.HT_FAKE, |
|
157 |
hvparams={}, |
|
158 |
beparams={}, |
|
159 |
osparams={}, |
|
160 |
admin_state=constants.ADMINST_DOWN, |
|
161 |
nics=[], |
|
162 |
disks=[], |
|
163 |
disk_template=constants.DT_DISKLESS, |
|
164 |
disks_active=False, |
|
165 |
network_port=None): |
|
166 |
"""Add a new L{objects.Instance} to the cluster configuration |
|
167 |
|
|
168 |
See L{objects.Instance} for parameter documentation. |
|
169 |
|
|
170 |
@rtype: L{objects.Instance} |
|
171 |
@return: the newly added instance |
|
172 |
|
|
173 |
""" |
|
174 |
inst_id = self._cur_inst_id |
|
175 |
self._cur_inst_id += 1 |
|
176 |
|
|
177 |
if uuid is None: |
|
178 |
uuid = self._GetUuid() |
|
179 |
if name is None: |
|
180 |
name = "mock_inst_%d.example.com" % inst_id |
|
181 |
if primary_node is None: |
|
182 |
primary_node = self._master_node.uuid |
|
183 |
if isinstance(primary_node, objects.Node): |
|
184 |
primary_node = self._master_node.uuid |
|
185 |
|
|
186 |
inst = objects.Instance(uuid=uuid, |
|
187 |
name=name, |
|
188 |
primary_node=primary_node, |
|
189 |
os=os, |
|
190 |
hypervisor=hypervisor, |
|
191 |
hvparams=hvparams, |
|
192 |
beparams=beparams, |
|
193 |
osparams=osparams, |
|
194 |
admin_state=admin_state, |
|
195 |
nics=nics, |
|
196 |
disks=disks, |
|
197 |
disk_template=disk_template, |
|
198 |
disks_active=disks_active, |
|
199 |
network_port=network_port) |
|
200 |
self.AddInstance(inst, None) |
|
201 |
return inst |
|
202 |
|
|
203 |
def _OpenConfig(self, accept_foreign): |
|
204 |
self._config_data = objects.ConfigData( |
|
205 |
version=constants.CONFIG_VERSION, |
|
206 |
cluster=None, |
|
207 |
nodegroups={}, |
|
208 |
nodes={}, |
|
209 |
instances={}, |
|
210 |
networks={}) |
|
211 |
|
|
212 |
master_node_uuid = self._GetUuid() |
|
213 |
|
|
214 |
self._cluster = objects.Cluster( |
|
215 |
serial_no=1, |
|
216 |
rsahostkeypub="", |
|
217 |
highest_used_port=(constants.FIRST_DRBD_PORT - 1), |
|
218 |
mac_prefix="aa:00:00", |
|
219 |
volume_group_name="xenvg", |
|
220 |
drbd_usermode_helper="/bin/true", |
|
221 |
nicparams={constants.PP_DEFAULT: constants.NICC_DEFAULTS}, |
|
222 |
ndparams=constants.NDC_DEFAULTS, |
|
223 |
tcpudp_port_pool=set(), |
|
224 |
enabled_hypervisors=[constants.HT_FAKE], |
|
225 |
master_node=master_node_uuid, |
|
226 |
master_ip="192.168.0.254", |
|
227 |
master_netdev=constants.DEFAULT_BRIDGE, |
|
228 |
cluster_name="cluster.example.com", |
|
229 |
file_storage_dir="/tmp", |
|
230 |
uid_pool=[], |
|
231 |
) |
|
232 |
self._config_data.cluster = self._cluster |
|
233 |
|
|
234 |
self._default_group = self.AddNewNodeGroup(name="default") |
|
235 |
self._master_node = self.AddNewNode(uuid=master_node_uuid) |
|
236 |
|
|
237 |
def _WriteConfig(self, destination=None, feedback_fn=None): |
|
238 |
pass |
|
239 |
|
|
240 |
def _DistributeConfig(self, feedback_fn): |
|
241 |
pass |
|
242 |
|
|
243 |
def _GetRpc(self, address_list): |
|
244 |
raise NotImplementedError |
b/test/py/cmdlib/testsupport/iallocator_mock.py | ||
---|---|---|
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 |
import mock |
|
22 |
|
|
23 |
from ganeti.masterd import iallocator |
|
24 |
|
|
25 |
|
|
26 |
def CreateIAllocatorMock(): |
|
27 |
"""Creates a new L{mock.MagicMock} tailored for L{iallocator.IAllocator} |
|
28 |
|
|
29 |
""" |
|
30 |
ret = mock.MagicMock(spec=iallocator.IAllocator) |
|
31 |
return ret |
b/test/py/cmdlib/testsupport/lock_manager_mock.py | ||
---|---|---|
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 |
from ganeti import locking |
|
22 |
|
|
23 |
|
|
24 |
class LockManagerMock(locking.GanetiLockManager): |
|
25 |
"""Mocked lock manager for tests. |
|
26 |
|
|
27 |
""" |
|
28 |
def __init__(self): |
|
29 |
# reset singleton instance, there is a separate lock manager for every test |
|
30 |
self.__class__._instance = None |
|
31 |
|
|
32 |
super(LockManagerMock, self).__init__([], [], [], []) |
|
33 |
|
|
34 |
def AddLocksFromConfig(self, cfg): |
|
35 |
"""Create locks for all entities in the given configuration. |
|
36 |
|
|
37 |
@type cfg: ganeti.config.ConfigWriter |
|
38 |
""" |
|
39 |
try: |
|
40 |
self.acquire(locking.LEVEL_CLUSTER, locking.BGL) |
|
41 |
|
|
42 |
for node_uuid in cfg.GetNodeList(): |
|
43 |
self.add(locking.LEVEL_NODE, node_uuid) |
|
44 |
self.add(locking.LEVEL_NODE_RES, node_uuid) |
|
45 |
for group_uuid in cfg.GetNodeGroupList(): |
|
46 |
self.add(locking.LEVEL_NODEGROUP, group_uuid) |
|
47 |
for inst in cfg.GetAllInstancesInfo().values(): |
|
48 |
self.add(locking.LEVEL_INSTANCE, inst.name) |
|
49 |
for net_uuid in cfg.GetNetworkList(): |
|
50 |
self.add(locking.LEVEL_NETWORK, net_uuid) |
|
51 |
finally: |
|
52 |
self.release(locking.LEVEL_CLUSTER, locking.BGL) |
b/test/py/cmdlib/testsupport/processor_mock.py | ||
---|---|---|
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 |
import re |
|
22 |
|
|
23 |
from ganeti import constants |
|
24 |
from ganeti import mcpu |
|
25 |
|
|
26 |
|
|
27 |
class LogRecordingCallback(mcpu.OpExecCbBase): |
|
28 |
"""Helper class for log output recording. |
|
29 |
|
|
30 |
""" |
|
31 |
def __init__(self, processor): |
|
32 |
self.processor = processor |
|
33 |
|
|
34 |
def Feedback(self, *args): |
|
35 |
assert len(args) < 3 |
|
36 |
|
|
37 |
if len(args) == 1: |
|
38 |
log_type = constants.ELOG_MESSAGE |
|
39 |
log_msg = args[0] |
|
40 |
else: |
|
41 |
(log_type, log_msg) = args |
|
42 |
|
|
43 |
self.processor.log_entries.append((log_type, log_msg)) |
|
44 |
|
|
45 |
|
|
46 |
class ProcessorMock(mcpu.Processor): |
|
47 |
"""Mocked opcode processor for tests. |
|
48 |
|
|
49 |
This class actually performs much more than a mock, as it drives the |
|
50 |
execution of LU's. But it also provides access to the log output of the LU |
|
51 |
the result of the execution. |
|
52 |
|
|
53 |
See L{ExecOpCodeAndRecordOutput} for the main method of this class. |
|
54 |
|
|
55 |
""" |
|
56 |
|
|
57 |
def __init__(self, context): |
|
58 |
super(ProcessorMock, self).__init__(context, 0, True) |
|
59 |
self.log_entries = [] |
|
60 |
|
|
61 |
def ExecOpCodeAndRecordOutput(self, op): |
|
62 |
"""Executes the given opcode and records the output for further inspection. |
|
63 |
|
|
64 |
@param op: the opcode to execute. |
|
65 |
@return: see L{mcpu.Processor.ExecOpCode} |
|
66 |
|
|
67 |
""" |
|
68 |
return self.ExecOpCode(op, LogRecordingCallback(self)) |
|
69 |
|
|
70 |
def GetLogEntries(self): |
|
71 |
"""Return the list of recorded log entries. |
|
72 |
|
|
73 |
@rtype: list of (string, string) tuples |
|
74 |
@return: the list of recorded log entries |
|
75 |
|
|
76 |
""" |
|
77 |
return self.log_entries |
|
78 |
|
|
79 |
def GetLogMessages(self): |
|
80 |
"""Return the list of recorded log messages. |
|
81 |
|
|
82 |
@rtype: list of string |
|
83 |
@return: the list of recorded log messages |
|
84 |
|
|
85 |
""" |
|
86 |
return [msg for _, msg in self.log_entries] |
|
87 |
|
|
88 |
def GetLogEntriesString(self): |
|
89 |
"""Return a string with all log entries separated by a newline. |
|
90 |
|
|
91 |
""" |
|
92 |
return "\n".join("%s: %s" % (type, msg) |
|
93 |
for type, msg in self.GetLogEntries()) |
|
94 |
|
|
95 |
def GetLogMessagesString(self): |
|
96 |
"""Return a string with all log messages separated by a newline. |
|
97 |
|
|
98 |
""" |
|
99 |
return "\n".join("%s" % msg for _, msg in self.GetLogEntries()) |
|
100 |
|
|
101 |
def assertLogContainsEntry(self, expected_type, expected_msg): |
|
102 |
"""Asserts that the log contains the exact given entry. |
|
103 |
|
|
104 |
@type expected_type: string |
|
105 |
@param expected_type: the expected type |
|
106 |
@type expected_msg: string |
|
107 |
@param expected_msg: the expected message |
|
108 |
|
|
109 |
""" |
|
110 |
for type, msg in self.log_entries: |
|
111 |
if type == expected_type and msg == expected_msg: |
|
112 |
return |
|
113 |
|
|
114 |
raise AssertionError( |
|
115 |
"Could not find '%s' (type '%s') in LU log messages. Log is:\n%s" % |
|
116 |
(expected_msg, expected_type, self.GetLogEntriesString())) |
|
117 |
|
|
118 |
def assertLogContainsMessage(self, expected_msg): |
|
119 |
"""Asserts that the log contains the exact given message. |
|
120 |
|
|
121 |
@type expected_msg: string |
|
122 |
@param expected_msg: the expected message |
|
123 |
|
|
124 |
""" |
|
125 |
for msg in self.GetLogMessages(): |
|
126 |
if msg == expected_msg: |
|
127 |
return |
|
128 |
|
|
129 |
raise AssertionError( |
|
130 |
"Could not find '%s' in LU log messages. Log is:\n%s" % |
|
131 |
(expected_msg, self.GetLogMessagesString())) |
|
132 |
|
|
133 |
def assertLogContainsRegex(self, expected_regex): |
|
134 |
"""Asserts that the log contains a message which matches the regex. |
|
135 |
|
|
136 |
@type expected_regex: string |
|
137 |
@param expected_regex: regular expression to match messages with. |
|
138 |
|
|
139 |
""" |
|
140 |
for msg in self.GetLogMessages(): |
|
141 |
if re.search(expected_regex, msg) is not None: |
|
142 |
return |
|
143 |
|
|
144 |
raise AssertionError( |
|
145 |
"Could not find '%s' in LU log messages. Log is:\n%s" % |
|
146 |
(expected_regex, self.GetLogMessagesString()) |
|
147 |
) |
b/test/py/cmdlib/testsupport/rpc_runner_mock.py | ||
---|---|---|
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 |
import mock |
|
22 |
|
|
23 |
from ganeti import objects |
|
24 |
from ganeti import rpc |
|
25 |
|
|
26 |
|
|
27 |
def CreateRpcRunnerMock(): |
|
28 |
"""Creates a new L{mock.MagicMock} tailored for L{rpc.RpcRunner} |
|
29 |
|
|
30 |
""" |
|
31 |
ret = mock.MagicMock(spec=rpc.RpcRunner) |
|
32 |
return ret |
|
33 |
|
|
34 |
|
|
35 |
class RpcResultsBuilder(object): |
|
36 |
"""Helper class which assists in constructing L{rpc.RpcResult} objects. |
|
37 |
|
|
38 |
This class provides some convenience methods for constructing L{rpc.RpcResult} |
|
39 |
objects. It is possible to create single results with the C{Create*} methods |
|
40 |
or to create multi-node results by repeatedly calling the C{Add*} methods and |
|
41 |
then obtaining the final result with C{Build}. |
|
42 |
|
|
43 |
The C{node} parameter of all the methods can either be a L{objects.Node} |
|
44 |
object, a node UUID or a node name. You have to provide the cluster config |
|
45 |
in the constructor if you want to use node UUID's/names. |
|
46 |
|
|
47 |
A typical usage of this class is as follows:: |
|
48 |
|
|
49 |
self.rpc.call_some_rpc.return_value = \ |
|
50 |
RpcResultsBuilder(cfg=self.cfg) \ |
|
51 |
.AddSuccessfulNode(node1, |
|
52 |
{ |
|
53 |
"result_key": "result_data", |
|
54 |
"another_key": "other_data", |
|
55 |
}) \ |
|
56 |
.AddErrorNode(node2) \ |
|
57 |
.Build() |
|
58 |
|
|
59 |
""" |
|
60 |
|
|
61 |
def __init__(self, cfg=None, use_node_names=False): |
|
62 |
"""Constructor. |
|
63 |
|
|
64 |
@type cfg: L{ganeti.config.ConfigWriter} |
|
65 |
@param cfg: used to resolve nodes if not C{None} |
|
66 |
@type use_node_names: bool |
|
67 |
@param use_node_names: if set to C{True}, the node field in the RPC results |
|
68 |
will contain the node name instead of the node UUID. |
|
69 |
""" |
|
70 |
self._cfg = cfg |
|
71 |
self._use_node_names = use_node_names |
|
72 |
self._results = [] |
|
73 |
|
|
74 |
def _GetNode(self, node_id): |
|
75 |
if isinstance(node_id, objects.Node): |
|
76 |
return node_id |
|
77 |
|
|
78 |
node = None |
|
79 |
if self._cfg is not None: |
|
80 |
node = self._cfg.GetNodeInfo(node_id) |
|
81 |
if node is None: |
|
82 |
node = self._cfg.GetNodeInfoByName(node_id) |
|
83 |
|
|
84 |
assert node is not None, "Failed to find '%s' in configuration" % node_id |
|
85 |
return node |
|
86 |
|
|
87 |
def _GetNodeId(self, node_id): |
|
88 |
node = self._GetNode(node_id) |
|
89 |
if self._use_node_names: |
|
90 |
return node.name |
|
91 |
else: |
|
92 |
return node.uuid |
|
93 |
|
|
94 |
def CreateSuccessfulNodeResult(self, node, data={}): |
|
95 |
"""@see L{RpcResultsBuilder} |
|
96 |
|
|
97 |
@param node: @see L{RpcResultsBuilder}. |
|
98 |
@type data: dict |
|
99 |
@param data: the data as returned by the RPC |
|
100 |
@rtype: L{rpc.RpcResult} |
|
101 |
""" |
|
102 |
return rpc.RpcResult(data=(True, data), node=self._GetNodeId(node)) |
|
103 |
|
|
104 |
def CreateFailedNodeResult(self, node): |
|
105 |
"""@see L{RpcResultsBuilder} |
|
106 |
|
|
107 |
@param node: @see L{RpcResultsBuilder}. |
|
108 |
@rtype: L{rpc.RpcResult} |
|
109 |
""" |
|
110 |
return rpc.RpcResult(failed=True, node=self._GetNodeId(node)) |
|
111 |
|
|
112 |
def CreateOfflineNodeResult(self, node): |
|
113 |
"""@see L{RpcResultsBuilder} |
|
114 |
|
|
115 |
@param node: @see L{RpcResultsBuilder}. |
|
116 |
@rtype: L{rpc.RpcResult} |
|
117 |
""" |
|
118 |
return rpc.RpcResult(failed=True, node=self._GetNodeId(node)) |
|
119 |
|
|
120 |
def CreateErrorNodeResult(self, node, error_msg=None): |
|
121 |
"""@see L{RpcResultsBuilder} |
|
122 |
|
|
123 |
@param node: @see L{RpcResultsBuilder}. |
|
124 |
@type error_msg: string |
|
125 |
@param error_msg: the error message as returned by the RPC |
|
126 |
@rtype: L{rpc.RpcResult} |
|
127 |
""" |
|
128 |
return rpc.RpcResult(data=(False, error_msg), node=self._GetNodeId(node)) |
|
129 |
|
|
130 |
def AddSuccessfulNode(self, node, data={}): |
|
131 |
"""@see L{CreateSuccessfulNode}""" |
|
132 |
self._results.append(self.CreateSuccessfulNodeResult(node, data)) |
|
133 |
return self |
|
134 |
|
|
135 |
def AddFailedNode(self, node): |
|
136 |
"""@see L{CreateFailedNode}""" |
|
137 |
self._results.append(self.CreateFailedNodeResult(node)) |
|
138 |
return self |
|
139 |
|
|
140 |
def AddOfflineNode(self, node): |
|
141 |
"""@see L{CreateOfflineNode}""" |
|
142 |
self._results.append(self.CreateOfflineNodeResult(node)) |
|
143 |
|
|
144 |
def AddErrorNode(self, node, error_msg=None): |
|
145 |
"""@see L{CreateErrorNode}""" |
|
146 |
self._results.append(self.CreateErrorNodeResult(node, error_msg=error_msg)) |
|
147 |
|
|
148 |
def Build(self): |
|
149 |
"""Creates a dictionary holding multi-node results |
|
150 |
|
|
151 |
@rtype: dict |
|
152 |
""" |
|
153 |
return dict((result.node, result) for result in self._results) |
Also available in: Unified diff