Revision e969a81f

b/lib/cmdlib/test.py
252 252
      if self.op.hypervisor is None:
253 253
        self.op.hypervisor = self.cfg.GetHypervisorType()
254 254
    elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
255
      (fuuid, fname) = ExpandInstanceUuidAndName(self.cfg, None, self.op.name)
256
      self.op.name = fname
255
      (self.inst_uuid, self.op.name) = ExpandInstanceUuidAndName(self.cfg, None,
256
                                                                 self.op.name)
257 257
      self.relocate_from_node_uuids = \
258
          list(self.cfg.GetInstanceInfo(fuuid).secondary_nodes)
258
          list(self.cfg.GetInstanceInfo(self.inst_uuid).secondary_nodes)
259 259
    elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
260 260
                          constants.IALLOCATOR_MODE_NODE_EVAC):
261 261
      if not self.op.instances:
......
307 307
                                             nics=self.op.nics,
308 308
                                             vcpus=self.op.vcpus,
309 309
                                             spindle_use=self.op.spindle_use,
310
                                             hypervisor=self.op.hypervisor)
310
                                             hypervisor=self.op.hypervisor,
311
                                             node_whitelist=None)
311 312
               for idx in range(self.op.count)]
312 313
      req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
313 314
    else:
b/test/py/cmdlib/test_unittest.py
21 21

  
22 22
"""Tests for LUTest*"""
23 23

  
24
import mock
24 25

  
25 26
from ganeti import constants
26
from ganeti import errors
27 27
from ganeti import opcodes
28 28

  
29 29
from testsupport import *
......
47 47
  def testInvalidDuration(self):
48 48
    op = opcodes.OpTestDelay(duration=-1)
49 49

  
50
    self.assertRaises(errors.OpExecError, self.ExecOpCode, op)
50
    self.ExecOpCodeExpectOpExecError(op)
51 51

  
52 52
  def testOnNodeUuid(self):
53 53
    node_uuids = [self.cfg.GetMasterNode()]
......
87 87
        .AddFailedNode(self.cfg.GetMasterNode()) \
88 88
        .Build()
89 89

  
90
    self.assertRaises(errors.OpExecError, self.ExecOpCode, op)
90
    self.ExecOpCodeExpectOpExecError(op)
91 91

  
92 92
  def testMultipleNodes(self):
93 93
    node1 = self.cfg.AddNewNode()
......
107 107
                                                     DELAY_DURATION)
108 108

  
109 109

  
110
class TestLUTestAllocator(CmdlibTestCase):
111
  def setUp(self):
112
    super(TestLUTestAllocator, self).setUp()
113

  
114
    self.base_op = opcodes.OpTestAllocator(
115
                      name="new-instance.example.com",
116
                      nics=[],
117
                      disks=[],
118
                      disk_template=constants.DT_DISKLESS,
119
                      direction=constants.IALLOCATOR_DIR_OUT,
120
                      iallocator="test")
121

  
122
    self.valid_alloc_op = \
123
      self.CopyOpCode(self.base_op,
124
                      mode=constants.IALLOCATOR_MODE_ALLOC,
125
                      memory=0,
126
                      disk_template=constants.DT_DISKLESS,
127
                      os="mock_os",
128
                      vcpus=1)
129
    self.valid_multi_alloc_op = \
130
      self.CopyOpCode(self.base_op,
131
                      mode=constants.IALLOCATOR_MODE_MULTI_ALLOC,
132
                      instances=["new-instance.example.com"],
133
                      memory=0,
134
                      disk_template=constants.DT_DISKLESS,
135
                      os="mock_os",
136
                      vcpus=1)
137
    self.valid_reloc_op = \
138
      self.CopyOpCode(self.base_op,
139
                      mode=constants.IALLOCATOR_MODE_RELOC)
140
    self.valid_chg_group_op = \
141
      self.CopyOpCode(self.base_op,
142
                      mode=constants.IALLOCATOR_MODE_CHG_GROUP,
143
                      instances=["new-instance.example.com"],
144
                      target_groups=["default"])
145
    self.valid_node_evac_op = \
146
      self.CopyOpCode(self.base_op,
147
                      mode=constants.IALLOCATOR_MODE_NODE_EVAC,
148
                      instances=["new-instance.example.com"],
149
                      evac_mode=constants.IALLOCATOR_NEVAC_PRI)
150

  
151
    self.iallocator_cls.return_value.in_text = "mock in text"
152
    self.iallocator_cls.return_value.out_text = "mock out text"
153

  
154
  def testMissingDirection(self):
155
    op = self.CopyOpCode(self.base_op,
156
                         direction=self.REMOVE)
157

  
158
    self.ExecOpCodeExpectOpPrereqError(
159
      op, "'OP_TEST_ALLOCATOR.direction' fails validation")
160

  
161
  def testAllocWrongDisks(self):
162
    op = self.CopyOpCode(self.valid_alloc_op,
163
                         disks=[0, "test"])
164

  
165
    self.ExecOpCodeExpectOpPrereqError(op, "Invalid contents")
166

  
167
  def testAllocWithExistingInstance(self):
168
    inst = self.cfg.AddNewInstance()
169
    op = self.CopyOpCode(self.valid_alloc_op, name=inst.name)
170

  
171
    self.ExecOpCodeExpectOpPrereqError(op, "already in the cluster")
172

  
173
  def testAllocMultiAllocMissingIAllocator(self):
174
    for mode in [constants.IALLOCATOR_MODE_ALLOC,
175
                 constants.IALLOCATOR_MODE_MULTI_ALLOC]:
176
      op = self.CopyOpCode(self.base_op,
177
                           mode=mode,
178
                           iallocator=None)
179

  
180
      self.ResetMocks()
181
      self.ExecOpCodeExpectOpPrereqError(op, "Missing allocator name")
182

  
183
  def testChgGroupNodeEvacMissingInstances(self):
184
    for mode in [constants.IALLOCATOR_MODE_CHG_GROUP,
185
                 constants.IALLOCATOR_MODE_NODE_EVAC]:
186
      op = self.CopyOpCode(self.base_op,
187
                           mode=mode)
188

  
189
      self.ResetMocks()
190
      self.ExecOpCodeExpectOpPrereqError(op, "Missing instances")
191

  
192
  def testAlloc(self):
193
    op = self.valid_alloc_op
194

  
195
    self.ExecOpCode(op)
196

  
197
    assert self.iallocator_cls.call_count == 1
198
    self.iallocator_cls.return_value.Run \
199
      .assert_called_once_with("test", validate=False)
200

  
201
  def testReloc(self):
202
    op = self.valid_reloc_op
203
    self.cfg.AddNewInstance(name=op.name)
204

  
205
    self.ExecOpCode(op)
206

  
207
    assert self.iallocator_cls.call_count == 1
208
    self.iallocator_cls.return_value.Run \
209
      .assert_called_once_with("test", validate=False)
210

  
211
  def testChgGroup(self):
212
    op = self.valid_chg_group_op
213
    for inst_name in op.instances:
214
      self.cfg.AddNewInstance(name=inst_name)
215

  
216
    self.ExecOpCode(op)
217

  
218
    assert self.iallocator_cls.call_count == 1
219
    self.iallocator_cls.return_value.Run \
220
      .assert_called_once_with("test", validate=False)
221

  
222
  def testNodeEvac(self):
223
    op = self.valid_node_evac_op
224
    for inst_name in op.instances:
225
      self.cfg.AddNewInstance(name=inst_name)
226

  
227
    self.ExecOpCode(op)
228

  
229
    assert self.iallocator_cls.call_count == 1
230
    self.iallocator_cls.return_value.Run \
231
      .assert_called_once_with("test", validate=False)
232

  
233
  def testMultiAlloc(self):
234
    op = self.valid_multi_alloc_op
235

  
236
    self.ExecOpCode(op)
237

  
238
    assert self.iallocator_cls.call_count == 1
239
    self.iallocator_cls.return_value.Run \
240
      .assert_called_once_with("test", validate=False)
241

  
242
  def testAllocDirectionIn(self):
243
    op = self.CopyOpCode(self.valid_alloc_op,
244
                         direction=constants.IALLOCATOR_DIR_IN)
245

  
246
    self.ExecOpCode(op)
247

  
248

  
110 249
if __name__ == "__main__":
111 250
  testutils.GanetiTestProgram()
b/test/py/cmdlib/testsupport/__init__.py
25 25

  
26 26
from cmdlib.testsupport.cmdlib_testcase import CmdlibTestCase
27 27
from cmdlib.testsupport.config_mock import ConfigMock
28
from cmdlib.testsupport.iallocator_mock import CreateIAllocatorMock
28
from cmdlib.testsupport.iallocator_mock import patchIAllocator
29 29
from cmdlib.testsupport.lock_manager_mock import LockManagerMock
30 30
from cmdlib.testsupport.processor_mock import ProcessorMock
31 31
from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock, \
......
33 33

  
34 34
__all__ = ["CmdlibTestCase",
35 35
           "ConfigMock",
36
           "CreateIAllocatorMock",
36
           "patchIAllocator",
37 37
           "CreateRpcRunnerMock",
38 38
           "LockManagerMock",
39 39
           "ProcessorMock",
b/test/py/cmdlib/testsupport/cmdlib_testcase.py
22 22
"""Main module of the cmdlib test framework"""
23 23

  
24 24

  
25
import inspect
26
import re
27
import traceback
28

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

  
35
from ganeti import errors
36
from ganeti import opcodes
37

  
31 38
import testutils
32 39

  
33 40

  
......
50 57
    * C{cfg}: @see L{ConfigMock}
51 58
    * C{glm}: @see L{LockManagerMock}
52 59
    * C{rpc}: @see L{CreateRpcRunnerMock}
53
    * C{iallocator}: @see L{CreateIAllocatorMock}
60
    * C{iallocator_cls}: @see L{patchIAllocator}
54 61
    * C{mcpu}: @see L{ProcessorMock}
55 62

  
56 63
  """
64

  
65
  REMOVE = object()
66

  
57 67
  def setUp(self):
58 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()
59 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
    """
60 98
    self.cfg = ConfigMock()
61 99
    self.glm = LockManagerMock()
62 100
    self.rpc = CreateRpcRunnerMock()
63
    self.iallocator = CreateIAllocatorMock()
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

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

  
67
  def tearDown(self):
68
    super(CmdlibTestCase, self).tearDown()
69

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

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

  
75 119
    """
76 120
    self.glm.AddLocksFromConfig(self.cfg)
77 121

  
78 122
    return self.mcpu.ExecOpCodeAndRecordOutput(opcode)
79 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

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

  
......
89 179
    """
90 180
    self.mcpu.assertLogContainsRegex(expected_regex)
91 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)
b/test/py/cmdlib/testsupport/config_mock.py
22 22
"""Support for mocking the cluster configuration"""
23 23

  
24 24

  
25
import time
25 26
import uuid as uuid_module
26 27

  
27 28
from ganeti import config
......
78 79
    if name is None:
79 80
      name = "mock_group_%d" % group_id
80 81
    if networks is None:
81
      networks = []
82
      networks = {}
82 83

  
83 84
    group = objects.NodeGroup(uuid=uuid,
84 85
                              name=name,
......
135 136
      group = self._default_group.uuid
136 137
    if isinstance(group, objects.NodeGroup):
137 138
      group = group.uuid
139
    if ndparams is None:
140
      ndparams = {}
138 141

  
139 142
    node = objects.Node(uuid=uuid,
140 143
                        name=name,
......
233 236
      serial_no=1,
234 237
      rsahostkeypub="",
235 238
      highest_used_port=(constants.FIRST_DRBD_PORT - 1),
239
      tcpudp_port_pool=set(),
236 240
      mac_prefix="aa:00:00",
237 241
      volume_group_name="xenvg",
242
      reserved_lvs=None,
238 243
      drbd_usermode_helper="/bin/true",
239
      nicparams={constants.PP_DEFAULT: constants.NICC_DEFAULTS},
240
      ndparams=constants.NDC_DEFAULTS,
241
      tcpudp_port_pool=set(),
242
      enabled_hypervisors=[constants.HT_FAKE],
243 244
      master_node=master_node_uuid,
244 245
      master_ip="192.168.0.254",
245 246
      master_netdev=constants.DEFAULT_BRIDGE,
247
      master_netmask=None,
248
      use_external_mip_script=None,
246 249
      cluster_name="cluster.example.com",
247 250
      file_storage_dir="/tmp",
248
      uid_pool=[],
251
      shared_file_storage_dir=None,
252
      enabled_hypervisors=list(constants.HYPER_TYPES),
253
      hvparams=None,
254
      ipolicy=None,
255
      os_hvp=None,
256
      beparams=None,
257
      osparams=None,
258
      nicparams=None,
259
      ndparams=None,
260
      diskparams=None,
261
      candidate_pool_size=3,
262
      modify_etc_hosts=False,
263
      modify_ssh_setup=False,
264
      maintain_node_health=False,
265
      uid_pool=None,
266
      default_iallocator=None,
267
      hidden_os=None,
268
      blacklisted_os=None,
269
      primary_ip_family=None,
270
      prealloc_wipe_disks=None,
271
      enabled_disk_templates=list(constants.DISK_TEMPLATES),
249 272
      )
273
    self._cluster.ctime = self._cluster.mtime = time.time()
274
    self._cluster.UpgradeConfig()
250 275
    self._config_data.cluster = self._cluster
251 276

  
252 277
    self._default_group = self.AddNewNodeGroup(name="default")
b/test/py/cmdlib/testsupport/iallocator_mock.py
24 24

  
25 25
import mock
26 26

  
27
from ganeti.masterd import iallocator
28 27

  
28
# pylint: disable=C0103
29
def patchIAllocator(module_under_test):
30
  """Patches the L{ganeti.masterd.iallocator.IAllocator} class for tests.
29 31

  
30
def CreateIAllocatorMock():
31
  """Creates a new L{mock.MagicMock} tailored for L{iallocator.IAllocator}
32
  This function is meant to be used as a decorator for test methods.
33

  
34
  @type module_under_test: string
35
  @param module_under_test: the module within cmdlib which is tested. The
36
        "ganeti.cmdlib" prefix is optional.
32 37

  
33 38
  """
34
  ret = mock.MagicMock(spec=iallocator.IAllocator)
35
  return ret
39
  if not module_under_test.startswith("ganeti.cmdlib"):
40
    module_under_test = "ganeti.cmdlib." + module_under_test
41
  return mock.patch("%s.iallocator.IAllocator" % module_under_test)

Also available in: Unified diff