~abentley/juju-ci-tools/client-from-config-4

« back to all changes in this revision

Viewing changes to tests/test_assess_recovery.py

  • Committer: Curtis Hovey
  • Date: 2015-12-20 15:14:05 UTC
  • Revision ID: curtis@canonical.com-20151220151405-pm3dauunjr2978gz
skip any client-server that starts with 1.26.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from contextlib import contextmanager
2
 
import logging
3
 
from mock import (
4
 
    call,
5
 
    patch,
6
 
    Mock,
7
 
    sentinel,
8
 
)
 
1
from argparse import Namespace
 
2
from mock import patch
9
3
 
10
4
from assess_recovery import (
11
 
    assess_recovery,
12
 
    delete_controller_members,
13
5
    main,
 
6
    make_client_from_args,
14
7
    parse_args,
15
8
)
 
9
from jujuconfig import get_jenv_path
16
10
from jujupy import (
17
 
    Machine,
 
11
    EnvJujuClient,
 
12
    SimpleEnvironment,
 
13
    _temp_env as temp_env,
18
14
)
19
15
from tests import (
20
16
    FakeHomeTestCase,
21
17
    TestCase,
22
18
)
23
 
from tests.test_jujupy import fake_juju_client
24
19
 
25
20
 
26
21
class TestParseArgs(TestCase):
27
22
 
28
23
    def test_parse_args(self):
29
 
        args = parse_args(['an-env', '/juju', 'log', 'tmp-env'])
30
 
        self.assertEqual(args.env, 'an-env')
31
 
        self.assertEqual(args.juju_bin, '/juju')
32
 
        self.assertEqual(args.logs, 'log')
33
 
        self.assertEqual(args.temp_env_name, 'tmp-env')
34
 
        self.assertEqual(args.charm_series, '')
 
24
        args = parse_args(['foo', 'bar', 'baz'])
 
25
        self.assertEqual(args.juju_path, 'foo')
 
26
        self.assertEqual(args.env_name, 'bar')
 
27
        self.assertEqual(args.logs, 'baz')
 
28
        self.assertEqual(args.charm_prefix, '')
35
29
        self.assertEqual(args.strategy, 'backup')
36
 
        self.assertEqual(args.verbose, logging.INFO)
37
30
        self.assertEqual(args.debug, False)
38
31
        self.assertIs(args.agent_stream, None)
39
32
        self.assertIs(args.series, None)
40
33
 
41
34
    def test_parse_args_ha(self):
42
 
        args = parse_args(['an-env', '/juju', 'log', 'tmp-env', '--ha'])
 
35
        args = parse_args(['foo', 'bar', 'baz', '--ha'])
43
36
        self.assertEqual(args.strategy, 'ha')
44
37
 
45
38
    def test_parse_args_ha_backup(self):
46
 
        args = parse_args(['an-env', '/juju', 'log', 'tmp-env', '--ha-backup'])
 
39
        args = parse_args(['foo', 'bar', 'baz', '--ha-backup'])
47
40
        self.assertEqual(args.strategy, 'ha-backup')
48
41
 
49
42
    def test_parse_args_backup(self):
50
 
        args = parse_args(['an-env', '/juju', 'log', 'tmp-env', '--ha',
51
 
                           '--backup'])
 
43
        args = parse_args(['foo', 'bar', 'baz', '--ha', '--backup'])
52
44
        self.assertEqual(args.strategy, 'backup')
53
45
 
54
 
    def test_parse_args_charm_series(self):
55
 
        args = parse_args(['an-env', '/juju', 'log', 'tmp-env',
56
 
                           '--charm-series', 'qux'])
57
 
        self.assertEqual(args.charm_series, 'qux')
58
 
 
59
 
 
60
 
class TestAssessRecovery(TestCase):
61
 
 
62
 
    @contextmanager
63
 
    def assess_recovery_cxt(self, client):
64
 
        client.bootstrap()
65
 
 
66
 
        def terminate(env, instance_ids):
67
 
            model = client._backend.controller_state.controller_model
68
 
            for instance_id in instance_ids:
69
 
                model.remove_state_server(instance_id)
70
 
 
71
 
        with patch('assess_recovery.wait_for_state_server_to_shutdown',
72
 
                   autospec=True):
73
 
            with patch('assess_recovery.terminate_instances',
74
 
                       side_effect=terminate):
75
 
                with patch('deploy_stack.wait_for_port', autospec=True):
76
 
                    yield
77
 
 
78
 
    def test_backup(self):
79
 
        client = fake_juju_client()
80
 
        bs_manager = Mock(client=client, known_hosts={})
81
 
        with self.assess_recovery_cxt(client):
82
 
            assess_recovery(bs_manager, 'backup', 'trusty')
83
 
 
84
 
    def test_ha(self):
85
 
        client = fake_juju_client()
86
 
        bs_manager = Mock(client=client, known_hosts={})
87
 
        with self.assess_recovery_cxt(client):
88
 
            assess_recovery(bs_manager, 'ha', 'trusty')
89
 
 
90
 
    def test_ha_backup(self):
91
 
        client = fake_juju_client()
92
 
        bs_manager = Mock(client=client, known_hosts={})
93
 
        with self.assess_recovery_cxt(client):
94
 
            assess_recovery(bs_manager, 'ha-backup', 'trusty')
95
 
 
96
 
    def test_controller_model_backup(self):
97
 
        client = fake_juju_client()
98
 
        bs_manager = Mock(client=client, known_hosts={})
99
 
        with self.assess_recovery_cxt(client):
100
 
            assess_recovery(bs_manager, 'backup', 'trusty')
101
 
 
102
 
    def test_controller_model_ha(self):
103
 
        client = fake_juju_client()
104
 
        bs_manager = Mock(client=client, known_hosts={})
105
 
        with self.assess_recovery_cxt(client):
106
 
            assess_recovery(bs_manager, 'ha', 'trusty')
107
 
 
108
 
    def test_controller_model_ha_backup(self):
109
 
        client = fake_juju_client()
110
 
        bs_manager = Mock(client=client, known_hosts={})
111
 
        with self.assess_recovery_cxt(client):
112
 
            assess_recovery(bs_manager, 'ha-backup', 'trusty')
113
 
 
114
 
 
115
 
@patch('assess_recovery.configure_logging', autospec=True)
116
 
@patch('assess_recovery.BootstrapManager.booted_context', autospec=True)
 
46
    def test_parse_args_charm_prefix(self):
 
47
        args = parse_args(['foo', 'bar', 'baz', '--charm-prefix', 'qux'])
 
48
        self.assertEqual(args.charm_prefix, 'qux')
 
49
 
 
50
    def test_parse_args_debug(self):
 
51
        args = parse_args(['foo', 'bar', 'baz', '--debug'])
 
52
        self.assertEqual(args.debug, True)
 
53
 
 
54
    def test_parse_args_temp_env_name(self):
 
55
        args = parse_args(['foo', 'bar', 'baz'])
 
56
        self.assertIs(args.temp_env_name, None)
 
57
        args = parse_args(['foo', 'bar', 'baz', 'qux'])
 
58
        self.assertEqual(args.temp_env_name, 'qux')
 
59
 
 
60
    def test_parse_args_agent_stream(self):
 
61
        args = parse_args(['foo', 'bar', 'baz', '--agent-stream', 'qux'])
 
62
        self.assertEqual(args.agent_stream, 'qux')
 
63
 
 
64
    def test_parse_args_series(self):
 
65
        args = parse_args(['foo', 'bar', 'baz', '--series', 'qux'])
 
66
        self.assertEqual(args.series, 'qux')
 
67
 
 
68
 
 
69
class TestMakeClientFromArgs(TestCase):
 
70
 
 
71
    def test_make_client_from_args(self):
 
72
        with temp_env({'environments': {'foo': {}}}):
 
73
            with patch.object(EnvJujuClient, 'get_version', return_value=''):
 
74
                client = make_client_from_args(
 
75
                    Namespace(env_name='foo', juju_path='bar',
 
76
                              temp_env_name='temp-foo', debug=False,
 
77
                              agent_stream=None, series=None))
 
78
        self.assertEqual(client.env.config, {'name': 'temp-foo'})
 
79
        self.assertEqual(client.env.environment, 'temp-foo')
 
80
 
 
81
 
 
82
def make_mocked_client(name, status_error=None):
 
83
    client = EnvJujuClient(SimpleEnvironment(
 
84
        name, {'type': 'paas'}), '1.23', 'path')
 
85
    patch.object(client, 'wait_for_ha', autospec=True).start()
 
86
    patch.object(
 
87
        client, 'get_status', autospec=True, side_effect=status_error).start()
 
88
    patch.object(client, 'destroy_environment', autospec=True).start()
 
89
    patch.object(client, 'is_jes_enabled', autospec=True,
 
90
                 return_value=False).start()
 
91
    return client
 
92
 
 
93
 
 
94
@patch('deploy_stack.dump_env_logs_known_hosts', autospec=True)
 
95
@patch('assess_recovery.parse_new_state_server_from_error', autospec=True,
 
96
       return_value='new_host')
 
97
@patch('assess_recovery.wait_for_state_server_to_shutdown', autospec=True)
 
98
@patch('assess_recovery.delete_instance', autospec=True)
 
99
@patch('assess_recovery.deploy_stack', autospec=True, return_value='i_id')
 
100
@patch('deploy_stack.get_machine_dns_name', autospec=True,
 
101
       return_value='host')
 
102
@patch('subprocess.check_output', autospec=True)
 
103
@patch('subprocess.check_call', autospec=True)
 
104
@patch('sys.stderr', autospec=True)
117
105
class TestMain(FakeHomeTestCase):
118
106
 
119
 
    def test_main(self, mock_bc, mock_cl):
120
 
        client = Mock(spec=['is_jes_enabled', 'version'])
121
 
        client.version = '1.25.5'
122
 
        with patch('deploy_stack.client_from_config',
123
 
                   return_value=client) as mock_c:
124
 
            with patch('assess_recovery.assess_recovery',
125
 
                       autospec=True) as mock_assess:
126
 
                main(['an-env', '/juju', 'log_dir', 'tmp-env', '--backup',
127
 
                      '--charm-series', 'a-series'])
128
 
        mock_cl.assert_called_once_with(logging.INFO)
129
 
        mock_c.assert_called_once_with('an-env', '/juju', debug=False)
130
 
        self.assertEqual(mock_bc.call_count, 1)
131
 
        self.assertEqual(mock_assess.call_count, 1)
132
 
        bs_manager, strategy, series = mock_assess.call_args[0]
133
 
        self.assertEqual((bs_manager.client, strategy, series),
134
 
                         (client, 'backup', 'a-series'))
135
 
 
136
 
    def test_error(self, mock_bc, mock_cl):
137
 
        class FakeError(Exception):
138
 
            """Custom exception to validate error handling."""
139
 
        error = FakeError('An error during test')
140
 
        client = Mock(spec=['is_jes_enabled', 'version'])
141
 
        client.version = '2.0.0'
142
 
        with patch('deploy_stack.client_from_config',
143
 
                   return_value=client) as mock_c:
144
 
            with patch('assess_recovery.parse_new_state_server_from_error',
145
 
                       autospec=True, return_value='a-host') as mock_pe:
146
 
                with patch('assess_recovery.assess_recovery', autospec=True,
147
 
                           side_effect=error) as mock_assess:
148
 
                    with self.assertRaises(FakeError) as ctx:
149
 
                        main(['an-env', '/juju', 'log_dir', 'tmp-env', '--ha',
150
 
                              '--verbose', '--charm-series', 'a-series'])
151
 
                    self.assertIs(ctx.exception, error)
152
 
        mock_cl.assert_called_once_with(logging.DEBUG)
153
 
        mock_c.assert_called_once_with('an-env', '/juju', debug=False)
154
 
        mock_pe.assert_called_once_with(error)
155
 
        self.assertEqual(mock_bc.call_count, 1)
156
 
        self.assertEqual(mock_assess.call_count, 1)
157
 
        bs_manager, strategy, series = mock_assess.call_args[0]
158
 
        self.assertEqual((bs_manager.client, strategy, series),
159
 
                         (client, 'ha', 'a-series'))
160
 
        self.assertEqual(bs_manager.known_hosts['0'], 'a-host')
161
 
 
162
 
 
163
 
@patch('assess_recovery.wait_for_state_server_to_shutdown', autospec=True)
164
 
@patch('assess_recovery.terminate_instances', autospec=True)
165
 
class TestDeleteControllerMembers(FakeHomeTestCase):
166
 
 
167
 
    def test_delete_controller_members(self, ti_mock, wsss_mock):
168
 
        client = Mock(spec=['env', 'get_controller_members'])
169
 
        client.env = sentinel.env
170
 
        client.env.config = {'type': 'lxd'}
171
 
        client.get_controller_members.return_value = [
172
 
            Machine('3', {
173
 
                'dns-name': '10.0.0.3',
174
 
                'instance-id': 'juju-dddd-machine-3',
175
 
                'controller-member-status': 'has-vote'}),
176
 
            Machine('0', {
177
 
                'dns-name': '10.0.0.0',
178
 
                'instance-id': 'juju-aaaa-machine-0',
179
 
                'controller-member-status': 'has-vote'}),
180
 
            Machine('2', {
181
 
                'dns-name': '10.0.0.2',
182
 
                'instance-id': 'juju-cccc-machine-2',
183
 
                'controller-member-status': 'has-vote'}),
184
 
        ]
185
 
        deleted = delete_controller_members(client)
186
 
        self.assertEqual(['2', '0', '3'], deleted)
187
 
        client.get_controller_members.assert_called_once_with()
188
 
        # terminate_instance was call in the reverse order of members.
189
 
        self.assertEqual(
190
 
            [call(client.env, ['juju-cccc-machine-2']),
191
 
             call(client.env, ['juju-aaaa-machine-0']),
192
 
             call(client.env, ['juju-dddd-machine-3'])],
193
 
            ti_mock.mock_calls)
194
 
        self.assertEqual(
195
 
            [call('10.0.0.2', client, 'juju-cccc-machine-2', timeout=120),
196
 
             call('10.0.0.0', client, 'juju-aaaa-machine-0', timeout=120),
197
 
             call('10.0.0.3', client, 'juju-dddd-machine-3', timeout=120)],
198
 
            wsss_mock.mock_calls)
199
 
        self.assertEqual(
200
 
            self.log_stream.getvalue(),
201
 
            'INFO Instrumenting node failure for member 2:'
202
 
            ' juju-cccc-machine-2 at 10.0.0.2\n'
203
 
            'INFO Instrumenting node failure for member 0:'
204
 
            ' juju-aaaa-machine-0 at 10.0.0.0\n'
205
 
            'INFO Instrumenting node failure for member 3:'
206
 
            ' juju-dddd-machine-3 at 10.0.0.3\n')
207
 
 
208
 
    def test_delete_controller_members_leader_only(self, ti_mock, wsss_mock):
209
 
        client = Mock(spec=['env', 'get_controller_leader'])
210
 
        client.env = sentinel.env
211
 
        client.env.config = {'type': 'lxd'}
212
 
        client.get_controller_leader.return_value = Machine('3', {
213
 
            'dns-name': '10.0.0.3',
214
 
            'instance-id': 'juju-dddd-machine-3',
215
 
            'controller-member-status': 'has-vote'})
216
 
        deleted = delete_controller_members(client, leader_only=True)
217
 
        self.assertEqual(['3'], deleted)
218
 
        client.get_controller_leader.assert_called_once_with()
219
 
        ti_mock.assert_called_once_with(client.env, ['juju-dddd-machine-3'])
220
 
        wsss_mock.assert_called_once_with(
221
 
            '10.0.0.3', client, 'juju-dddd-machine-3', timeout=120)
222
 
        self.assertEqual(
223
 
            self.log_stream.getvalue(),
224
 
            'INFO Instrumenting node failure for member 3:'
225
 
            ' juju-dddd-machine-3 at 10.0.0.3\n')
226
 
 
227
 
    def test_delete_controller_members_azure(self, ti_mock, wsss_mock):
228
 
        client = Mock(spec=['env', 'get_controller_leader'])
229
 
        client.env = sentinel.env
230
 
        client.env.config = {'type': 'azure'}
231
 
        client.get_controller_leader.return_value = Machine('3', {
232
 
            'dns-name': '10.0.0.3',
233
 
            'instance-id': 'juju-dddd-machine-3',
234
 
            'controller-member-status': 'has-vote'})
235
 
        with patch('assess_recovery.convert_to_azure_ids', autospec=True,
236
 
                   return_value=['juju-azure-id']):
237
 
            deleted = delete_controller_members(client, leader_only=True)
238
 
        self.assertEqual(['3'], deleted)
239
 
        client.get_controller_leader.assert_called_once_with()
240
 
        ti_mock.assert_called_once_with(client.env, ['juju-azure-id'])
241
 
        wsss_mock.assert_called_once_with(
242
 
            '10.0.0.3', client, 'juju-azure-id', timeout=120)
243
 
        self.assertEqual(
244
 
            self.log_stream.getvalue(),
245
 
            'INFO Instrumenting node failure for member 3:'
246
 
            ' juju-azure-id at 10.0.0.3\n')
 
107
    def test_ha(self, so_mock, cc_mock, co_mock,
 
108
                dns_mock, ds_mock, di_mock, ws_mock, ns_mock, dl_mock):
 
109
        client = make_mocked_client('foo')
 
110
        with patch('assess_recovery.make_client_from_args', autospec=True,
 
111
                   return_value=client) as mc_mock:
 
112
            main(['./', 'foo', 'log_dir',
 
113
                  '--ha', '--charm-prefix', 'prefix'])
 
114
        mc_mock.assert_called_once_with(Namespace(
 
115
            agent_stream=None, charm_prefix='prefix', debug=False,
 
116
            env_name='foo', juju_path='./', logs='log_dir', strategy='ha',
 
117
            temp_env_name=None, series=None))
 
118
        client.wait_for_ha.assert_called_once_with()
 
119
        client.get_status.assert_called_once_with(600)
 
120
        self.assertEqual(2, client.destroy_environment.call_count)
 
121
        dns_mock.assert_called_once_with(client, '0')
 
122
        ds_mock.assert_called_once_with(client, 'prefix')
 
123
        di_mock.assert_called_once_with(client, 'i_id')
 
124
        ws_mock.assert_called_once_with('host', client, 'i_id')
 
125
        jenv_path = get_jenv_path(client.env.juju_home, client.env.environment)
 
126
        dl_mock.assert_called_once_with(client, 'log_dir', jenv_path,
 
127
                                        {})
 
128
        self.assertEqual(0, ns_mock.call_count)
 
129
 
 
130
    def test_ha_error(self, so_mock, cc_mock, co_mock,
 
131
                      dns_mock, ds_mock, di_mock, ws_mock, ns_mock, dl_mock):
 
132
        error = Exception()
 
133
        client = make_mocked_client('foo', status_error=error)
 
134
        with patch('assess_recovery.make_client_from_args', autospec=True,
 
135
                   return_value=client) as mc_mock:
 
136
            with self.assertRaises(SystemExit):
 
137
                    main(['./', 'foo', 'log_dir',
 
138
                          '--ha', '--charm-prefix', 'prefix'])
 
139
        mc_mock.assert_called_once_with(Namespace(
 
140
            agent_stream=None, charm_prefix='prefix', debug=False,
 
141
            env_name='foo', juju_path='./', logs='log_dir', strategy='ha',
 
142
            temp_env_name=None, series=None))
 
143
        client.wait_for_ha.assert_called_once_with()
 
144
        client.get_status.assert_called_once_with(600)
 
145
        self.assertEqual(2, client.destroy_environment.call_count)
 
146
        dns_mock.assert_called_once_with(client, '0')
 
147
        ds_mock.assert_called_once_with(client, 'prefix')
 
148
        di_mock.assert_called_once_with(client, 'i_id')
 
149
        ws_mock.assert_called_once_with('host', client, 'i_id')
 
150
        ns_mock.assert_called_once_with(error)
 
151
        jenv_path = get_jenv_path(client.env.juju_home, client.env.environment)
 
152
        dl_mock.assert_called_once_with(client, 'log_dir', jenv_path,
 
153
                                        {'0': 'new_host'})
 
154
 
 
155
    def test_destroy_on_boot_error(self, so_mock, cc_mock, co_mock,
 
156
                                   dns_mock, ds_mock, di_mock, ws_mock,
 
157
                                   ns_mock, dl_mock):
 
158
        client = make_mocked_client('foo')
 
159
        with patch('assess_recovery.make_client', autospec=True,
 
160
                   return_value=client):
 
161
            with patch.object(client, 'bootstrap', side_effect=Exception):
 
162
                with self.assertRaises(SystemExit):
 
163
                    main(['./', 'foo', 'log_dir',
 
164
                          '--ha', '--charm-prefix', 'prefix'])
 
165
        self.assertEqual(2, client.destroy_environment.call_count)