~ajkavanagh/charms/trusty/mongodb/fix-unit-test-lint-lp1533654

« back to all changes in this revision

Viewing changes to unit_tests/test_hooks.py

  • Committer: Jorge Niedbalski
  • Author(s): Mario Splivalo
  • Date: 2015-01-22 17:09:22 UTC
  • mfrom: (56.1.66 mongodb.replsets-fix-try)
  • Revision ID: jorge.niedbalski@canonical.com-20150122170922-nz94p2e0j4xqbbda
[mariosplivalo, r=niedbalski,freyes] Fixes various replicaset issues bug LP: #1403698, LP: #1370542, LP: #1379604

unit tests Ok, amulet tests OK.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from mock import patch, call
 
2
 
 
3
import hooks
 
4
 
 
5
from test_utils import CharmTestCase
 
6
from pymongo.errors import OperationFailure
 
7
from subprocess import CalledProcessError
 
8
 
 
9
# Defines a set of functions to patch on the hooks object. Any of these
 
10
# methods will be patched by default on the default invocations of the
 
11
# hooks.some_func(). Invoking the the interface change relations will cause
 
12
# the hooks context to be created outside of the normal mockery.
 
13
TO_PATCH = [
 
14
    'relation_id',
 
15
    'relation_get',
 
16
    'relation_set',
 
17
    'unit_get',
 
18
    'juju_log',
 
19
    'config',
 
20
]
 
21
 
 
22
 
 
23
class MongoHooksTest(CharmTestCase):
 
24
 
 
25
    def setUp(self):
 
26
        super(MongoHooksTest, self).setUp(hooks, TO_PATCH)
 
27
 
 
28
        # The self.config object can be used for direct invocations of the
 
29
        # hooks methods. The side_effect of invoking the config object within
 
30
        # the hooks object will return the value that is set in the test case's
 
31
        # test_config dictionary
 
32
        self.config.side_effect = self.test_config.get
 
33
        self.relation_get.side_effect = self.test_relation.get
 
34
 
 
35
    @patch.object(hooks, 'restart_mongod')
 
36
    @patch.object(hooks, 'enable_replset')
 
37
    # Note: patching the os.environ dictionary in-line here so there's no
 
38
    # additional parameter sent into the function
 
39
    @patch.dict('os.environ', JUJU_UNIT_NAME='fake-unit/0')
 
40
    def test_replica_set_relation_joined(self, mock_enable_replset,
 
41
                                         mock_restart):
 
42
        self.unit_get.return_value = 'private.address'
 
43
        self.test_config.set('port', '1234')
 
44
        self.test_config.set('replicaset', 'fake-replicaset')
 
45
        self.relation_id.return_value = 'fake-relation-id'
 
46
 
 
47
        mock_enable_replset.return_value = False
 
48
 
 
49
        hooks.replica_set_relation_joined()
 
50
 
 
51
        # Verify that mongodb was NOT restarted since the replicaset we claimed
 
52
        # was not enabled.
 
53
        self.assertFalse(mock_restart.called)
 
54
 
 
55
        exp_rel_vals = {'hostname': 'private.address',
 
56
                        'port': '1234',
 
57
                        'replset': 'fake-replicaset',
 
58
                        'install-order': '0',
 
59
                        'type': 'replset'}
 
60
        # Check that the relation data was set as we expect it to be set.
 
61
        self.relation_set.assert_called_with('fake-relation-id', exp_rel_vals)
 
62
 
 
63
        mock_enable_replset.reset_mock()
 
64
        self.relation_set.reset_mock()
 
65
        mock_enable_replset.return_value = True
 
66
 
 
67
        hooks.replica_set_relation_joined()
 
68
 
 
69
        self.assertTrue(mock_restart.called)
 
70
        self.relation_set.assert_called_with('fake-relation-id', exp_rel_vals)
 
71
 
 
72
    @patch.object(hooks, 'run_admin_command')
 
73
    @patch.object(hooks, 'Connection')
 
74
    @patch.object(hooks, 'config')
 
75
    @patch.object(hooks, 'mongo_client')
 
76
    @patch('time.sleep')
 
77
    def test_init_repl_set(self, mock_sleep, mock_mongo_client_fn,
 
78
                           mock_config, mock_mongo_client,
 
79
                           mock_run_admin_command):
 
80
        mock_mongo_client_fn.return_value = False
 
81
 
 
82
        mock_config.return_value = {'replicaset': 'foo',
 
83
                                    'private-address': 'mongo.local',
 
84
                                    'port': '12345'}
 
85
 
 
86
        # Put the OK state (1) at the end and check the loop.
 
87
        ret_values = [{'myState': x} for x in [0, 2, 5, 1]]
 
88
        mock_run_admin_command.side_effect = ret_values
 
89
 
 
90
        hooks.init_replset()
 
91
 
 
92
        mock_run_admin_command.assert_called()
 
93
        self.assertEqual(len(ret_values), mock_run_admin_command.call_count)
 
94
        self.assertEqual(len(ret_values) + 1, mock_sleep.call_count)
 
95
 
 
96
        mock_run_admin_command.reset_mock()
 
97
        exc = [OperationFailure('Received replSetInitiate'),
 
98
               OperationFailure('unhandled')]
 
99
        mock_run_admin_command.side_effect = exc
 
100
 
 
101
        try:
 
102
            hooks.init_replset()
 
103
            self.assertTrue(False, msg="Expected error")
 
104
        except OperationFailure:
 
105
            pass
 
106
 
 
107
        mock_run_admin_command.assert_called()
 
108
        self.assertEqual(2, mock_run_admin_command.call_count)
 
109
 
 
110
    @patch.object(hooks, 'mongo_client_smart')
 
111
    def test_join_replset(self, mock_mongo_client):
 
112
        hooks.join_replset()
 
113
        self.assertFalse(mock_mongo_client.called)
 
114
 
 
115
        mock_mongo_client.reset_mock()
 
116
        hooks.join_replset(master_node='mongo.local')
 
117
        self.assertFalse(mock_mongo_client.called)
 
118
 
 
119
        mock_mongo_client.reset_mock()
 
120
        hooks.join_replset(host='fake-host')
 
121
        self.assertFalse(mock_mongo_client.called)
 
122
 
 
123
        mock_mongo_client.reset_mock()
 
124
        hooks.join_replset(master_node='mongo.local', host='fake-host')
 
125
        mock_mongo_client.assert_called_with('localhost',
 
126
                                             'rs.add("fake-host")')
 
127
 
 
128
    @patch.object(hooks, 'mongo_client')
 
129
    def test_leave_replset(self, mock_mongo_client):
 
130
        hooks.leave_replset()
 
131
        self.assertFalse(mock_mongo_client.called)
 
132
 
 
133
        mock_mongo_client.reset_mock()
 
134
        hooks.leave_replset(master_node='mongo.local')
 
135
        self.assertFalse(mock_mongo_client.called)
 
136
 
 
137
        mock_mongo_client.reset_mock()
 
138
        hooks.leave_replset(host='fake-host')
 
139
        self.assertFalse(mock_mongo_client.called)
 
140
 
 
141
        mock_mongo_client.reset_mock()
 
142
        hooks.leave_replset('mongo.local', 'fake-host')
 
143
        mock_mongo_client.assert_called_with('mongo.local',
 
144
                                             'rs.remove("fake-host")')
 
145
 
 
146
    @patch.object(hooks, 'apt_install')
 
147
    @patch.object(hooks, 'apt_update')
 
148
    @patch.object(hooks, 'add_source')
 
149
    @patch.dict('os.environ', CHARM_DIR='/tmp/charm/dir')
 
150
    def test_install_hook(self, mock_add_source, mock_apt_update,
 
151
                          mock_apt_install):
 
152
        self.test_config.set('source', 'fake-source')
 
153
        self.test_config.set('key', 'fake-key')
 
154
 
 
155
        hooks.install_hook()
 
156
        mock_add_source.assert_called_with('fake-source', 'fake-key')
 
157
        mock_apt_update.assert_called_with(fatal=True)
 
158
        mock_apt_install.assert_called_with(packages=hooks.INSTALL_PACKAGES,
 
159
                                            fatal=True)
 
160
 
 
161
    @patch.object(hooks, 'run_admin_command')
 
162
    @patch.object(hooks, 'Connection')
 
163
    @patch('time.sleep')
 
164
    def test_am_i_primary(self, mock_sleep, mock_mongo_client,
 
165
                          mock_run_admin_cmd):
 
166
        mock_run_admin_cmd.side_effect = [{'myState': x} for x in xrange(5)]
 
167
        expected_results = [True if x == 1 else False for x in xrange(5)]
 
168
 
 
169
        # Check expected return values each time...
 
170
        for exp in expected_results:
 
171
            rv = hooks.am_i_primary()
 
172
            self.assertEqual(exp, rv)
 
173
 
 
174
    @patch.object(hooks, 'run_admin_command')
 
175
    @patch.object(hooks, 'Connection')
 
176
    @patch('time.sleep')
 
177
    def test_am_i_primary_too_many_attempts(self, mock_sleep,
 
178
                                            mock_mongo_client,
 
179
                                            mock_run_admin_cmd):
 
180
        msg = 'replSetInitiate - should come online shortly'
 
181
        mock_run_admin_cmd.side_effect = [OperationFailure(msg)
 
182
                                          for x in xrange(10)]
 
183
 
 
184
        try:
 
185
            hooks.am_i_primary()
 
186
            self.assertTrue(False, 'Expected failure.')
 
187
        except hooks.TimeoutException:
 
188
            self.assertEqual(mock_run_admin_cmd.call_count, 10)
 
189
            pass
 
190
 
 
191
    @patch.object(hooks, 'run_admin_command')
 
192
    @patch.object(hooks, 'Connection')
 
193
    @patch('time.sleep')
 
194
    def test_am_i_primary_operation_failures(self, mock_sleep,
 
195
                                             mock_mongo_client,
 
196
                                             mock_run_admin_cmd):
 
197
        mock_run_admin_cmd.side_effect = OperationFailure('EMPTYCONFIG')
 
198
 
 
199
        rv = hooks.am_i_primary()
 
200
        mock_run_admin_cmd.assert_called()
 
201
        self.assertFalse(rv)
 
202
 
 
203
        mock_run_admin_cmd.reset_mock()
 
204
        mock_run_admin_cmd.side_effect = OperationFailure('unexpected failure')
 
205
        try:
 
206
            hooks.am_i_primary()
 
207
            self.assertFalse(True, "Expected OperationFailure to be raised")
 
208
        except OperationFailure:
 
209
            mock_run_admin_cmd.assert_called()
 
210
 
 
211
    @patch('time.sleep')
 
212
    @patch('subprocess.check_output')
 
213
    def test_mongo_client_smart_no_command(self, mock_check_output,
 
214
                                           mock_sleep):
 
215
        rv = hooks.mongo_client_smart()
 
216
        self.assertFalse(rv)
 
217
        self.assertEqual(0, mock_check_output.call_count)
 
218
 
 
219
        mock_check_output.reset_mock()
 
220
        mock_check_output.return_value = '{"ok": 1}'
 
221
 
 
222
        rv = hooks.mongo_client_smart(command='fake-cmd')
 
223
        self.assertTrue(rv)
 
224
        mock_check_output.assert_called_once_with(['mongo', '--quiet',
 
225
                                                   '--host', 'localhost',
 
226
                                                   '--eval',
 
227
                                                   'printjson(fake-cmd)'])
 
228
 
 
229
    @patch('time.sleep')
 
230
    @patch('subprocess.check_output')
 
231
    def test_mongo_client_smart_error_cases(self, mock_ck_output, mock_sleep):
 
232
        mock_ck_output.side_effect = [CalledProcessError(1, 'cmd',
 
233
                                                         output='fake-error')
 
234
                                      for x in xrange(11)]
 
235
        rv = hooks.mongo_client_smart(command='fake-cmd')
 
236
        self.assertFalse(rv)
 
237
 
 
238
    @patch('subprocess.call')
 
239
    def test_mongo_client(self, mock_subprocess):
 
240
        rv = hooks.mongo_client()
 
241
        self.assertFalse(rv)
 
242
        self.assertEqual(0, mock_subprocess.call_count)
 
243
 
 
244
        mock_subprocess.reset_mock()
 
245
        rv = hooks.mongo_client(host='fake-host')
 
246
        self.assertFalse(rv)
 
247
        self.assertEqual(0, mock_subprocess.call_count)
 
248
 
 
249
        mock_subprocess.reset_mock()
 
250
        rv = hooks.mongo_client(command='fake-command')
 
251
        self.assertFalse(rv)
 
252
        self.assertEqual(0, mock_subprocess.call_count)
 
253
 
 
254
        mock_subprocess.reset_mock()
 
255
        mock_subprocess.return_value = 0
 
256
        rv = hooks.mongo_client(host='fake-host', command='fake-command')
 
257
        expected_cmd = ("mongo --host %s --eval 'printjson(%s)'"
 
258
                        % ('fake-host', 'fake-command'))
 
259
        mock_subprocess.assert_called_once_with(expected_cmd, shell=True)
 
260
        self.assertTrue(rv)
 
261
 
 
262
        mock_subprocess.reset_mock()
 
263
        mock_subprocess.return_value = 1
 
264
        rv = hooks.mongo_client(host='fake-host', command='fake-command')
 
265
        expected_cmd = ("mongo --host %s --eval 'printjson(%s)'"
 
266
                        % ('fake-host', 'fake-command'))
 
267
        mock_subprocess.assert_called_once_with(expected_cmd, shell=True)
 
268
        self.assertFalse(rv)
 
269
 
 
270
    @patch.object(hooks, 'am_i_primary')
 
271
    @patch.object(hooks, 'init_replset')
 
272
    @patch.object(hooks, 'relation_get')
 
273
    @patch.object(hooks, 'peer_units')
 
274
    @patch.object(hooks, 'oldest_peer')
 
275
    @patch.object(hooks, 'join_replset')
 
276
    @patch.object(hooks, 'unit_get')
 
277
    def test_replica_set_relation_changed(self, mock_unit_get,
 
278
                                          mock_join_replset, mock_oldest_peer,
 
279
                                          mock_peer_units, mock_relation_get,
 
280
                                          mock_init_replset, mock_is_primary):
 
281
        # set the unit_get('private-address')
 
282
        mock_unit_get.return_value = 'juju-local-unit-0.local'
 
283
        mock_relation_get.return_value = None
 
284
 
 
285
        # Test when remote hostname is None, should not join
 
286
        hooks.replica_set_relation_changed()
 
287
        self.assertEqual(0, mock_join_replset.call_count)
 
288
 
 
289
        # Test remote hostname is valid, but master is somehow not defined
 
290
        mock_join_replset.reset_mock()
 
291
        mock_relation_get.return_value = 'juju-local-unit-0'
 
292
 
 
293
        hooks.replica_set_relation_changed()
 
294
 
 
295
        self.assertEqual(1, mock_join_replset.call_count)
 
296
 
 
297
        # Test when not oldest peer, don't init replica set
 
298
        mock_init_replset.reset_mock()
 
299
        mock_oldest_peer.reset_mock()
 
300
        mock_peer_units.return_value = ['mongodb/1', 'mongodb/2']
 
301
        mock_oldest_peer.return_value = False
 
302
 
 
303
        hooks.replica_set_relation_changed()
 
304
 
 
305
        self.assertEqual(mock_init_replset.call_count, 0)
 
306
 
 
307
        # Test when its also the PRIMARY
 
308
        mock_relation_get.reset_mock()
 
309
        mock_relation_get.side_effect = ['juju-remote-unit-0', '12345']
 
310
        mock_oldest_peer.reset_mock()
 
311
        mock_oldest_peer.return_value = False
 
312
        mock_is_primary.reset_mock()
 
313
        mock_is_primary.return_value = True
 
314
 
 
315
        hooks.replica_set_relation_changed()
 
316
        call1 = call('juju-local-unit-0.local:27017',
 
317
                     'juju-remote-unit-0:12345')
 
318
        mock_join_replset.assert_has_calls(call1)
 
319
 
 
320
    @patch.object(hooks, 'unit_get')
 
321
    @patch.object(hooks, 'leave_replset')
 
322
    @patch.object(hooks, 'am_i_primary')
 
323
    def test_replica_set_relation_departed(self, mock_am_i_primary,
 
324
                                           mock_leave_replset, mock_unit_get):
 
325
        mock_am_i_primary.return_value = False
 
326
        hooks.replica_set_relation_departed()
 
327
 
 
328
        self.assertEqual(0, mock_leave_replset.call_count)
 
329
 
 
330
        mock_am_i_primary.reset_mock()
 
331
        mock_am_i_primary.return_value = True
 
332
        mock_unit_get.return_value = 'juju-local'
 
333
 
 
334
        self.test_relation.set({'hostname': 'juju-remote',
 
335
                                'port': '27017'})
 
336
        mock_leave_replset.reset_mock()
 
337
 
 
338
        hooks.replica_set_relation_departed()
 
339
 
 
340
        call1 = call('juju-local:27017', 'juju-remote:27017')
 
341
        mock_leave_replset.assert_has_calls(call1)