~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to tests/test_deploy_stack.py

  • Committer: Curtis Hovey
  • Date: 2016-06-15 20:52:35 UTC
  • Revision ID: curtis@canonical.com-20160615205235-cf6hu9xt1qmbo1a4
Escape vars in run-unit-tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
from argparse import (
2
2
    Namespace,
3
 
    )
 
3
)
4
4
from contextlib import contextmanager
5
 
from datetime import (
6
 
    datetime,
7
 
    timedelta,
8
 
    )
9
5
import json
10
6
import logging
11
7
import os
13
9
import sys
14
10
from unittest import (
15
11
    skipIf,
16
 
    )
 
12
)
17
13
 
18
14
from mock import (
19
15
    call,
20
16
    MagicMock,
21
17
    patch,
22
 
    )
 
18
)
23
19
import yaml
24
20
 
25
21
from deploy_stack import (
39
35
    destroy_environment,
40
36
    dump_env_logs,
41
37
    dump_juju_timings,
42
 
    _get_clients_to_upgrade,
43
38
    iter_remote_machines,
44
39
    get_remote_machines,
45
40
    GET_TOKEN_SCRIPT,
46
41
    safe_print_status,
47
42
    retain_config,
48
43
    update_env,
49
 
    )
50
 
from fakejuju import (
51
 
    fake_juju_client,
52
 
    fake_juju_client_optional_jes,
53
 
    )
 
44
)
54
45
from jujuconfig import (
55
46
    get_environments_path,
56
47
    get_jenv_path,
59
50
from jujupy import (
60
51
    EnvJujuClient,
61
52
    EnvJujuClient1X,
62
 
    EnvJujuClient25,
63
 
    EnvJujuClient26,
64
53
    get_cache_path,
65
54
    get_timeout_prefix,
66
55
    get_timeout_path,
68
57
    KILL_CONTROLLER,
69
58
    SimpleEnvironment,
70
59
    Status,
71
 
    )
 
60
)
72
61
from remote import (
73
62
    _Remote,
74
63
    remote_from_address,
75
64
    SSHRemote,
76
 
    winrm,
77
 
    )
 
65
)
78
66
from tests import (
79
67
    FakeHomeTestCase,
80
68
    temp_os_env,
81
69
    use_context,
82
 
    )
 
70
)
83
71
from tests.test_jujupy import (
84
72
    assert_juju_call,
 
73
    fake_juju_client,
 
74
    fake_juju_client_optional_jes,
85
75
    FakePopen,
86
76
    observable_temp_file,
87
 
    )
 
77
)
88
78
from utility import (
89
79
    LoggedException,
90
80
    temp_dir,
91
 
    )
 
81
)
92
82
 
93
83
 
94
84
def make_logs(log_dir):
219
209
    def test_check_token(self):
220
210
        env = JujuData('foo', {'type': 'local'})
221
211
        client = EnvJujuClient(env, None, None)
222
 
        status = Status.from_text("""\
223
 
            applications:
224
 
              dummy-sink:
225
 
                units:
226
 
                  dummy-sink/0:
227
 
                    workload-status:
228
 
                      current: active
229
 
                      message: Token is token
230
 
 
231
 
            """)
232
212
        remote = SSHRemote(client, 'unit', None, series='xenial')
233
213
        with patch('deploy_stack.remote_from_unit', autospec=True,
234
214
                   return_value=remote):
235
215
            with patch.object(remote, 'run', autospec=True,
236
216
                              return_value='token') as rr_mock:
237
 
                with patch.object(client, 'get_status', autospec=True,
238
 
                                  return_value=status):
239
 
                    check_token(client, 'token', timeout=0)
 
217
                check_token(client, 'token', timeout=0)
240
218
        rr_mock.assert_called_once_with(GET_TOKEN_SCRIPT)
241
219
        self.assertTrue(remote.use_juju_ssh)
242
220
        self.assertEqual(
243
 
            ['INFO Waiting for applications to reach ready.',
244
 
             'INFO Retrieving token.',
 
221
            ['INFO Retrieving token.',
245
222
             "INFO Token matches expected 'token'"],
246
223
            self.log_stream.getvalue().splitlines())
247
224
 
248
225
    def test_check_token_not_found(self):
249
226
        env = JujuData('foo', {'type': 'local'})
250
227
        client = EnvJujuClient(env, None, None)
251
 
        status = Status.from_text("""\
252
 
            applications:
253
 
              dummy-sink:
254
 
                units:
255
 
                  dummy-sink/0:
256
 
                    workload-status:
257
 
                      current: active
258
 
                      message: Waiting for token
259
 
 
260
 
            """)
261
228
        remote = SSHRemote(client, 'unit', None, series='xenial')
262
229
        with patch('deploy_stack.remote_from_unit', autospec=True,
263
230
                   return_value=remote):
265
232
                              return_value='') as rr_mock:
266
233
                with patch.object(remote, 'get_address',
267
234
                                  autospec=True) as ga_mock:
268
 
                    with patch.object(client, 'get_status', autospec=True,
269
 
                                      return_value=status):
270
 
                        with self.assertRaisesRegexp(ValueError,
271
 
                                                     "Token is ''"):
272
 
                            check_token(client, 'token', timeout=0)
 
235
                    with self.assertRaisesRegexp(ValueError, "Token is ''"):
 
236
                        check_token(client, 'token', timeout=0)
273
237
        self.assertEqual(2, rr_mock.call_count)
274
238
        rr_mock.assert_called_with(GET_TOKEN_SCRIPT)
275
239
        ga_mock.assert_called_once_with()
276
240
        self.assertFalse(remote.use_juju_ssh)
277
241
        self.assertEqual(
278
 
            ['INFO Waiting for applications to reach ready.',
279
 
             'INFO Retrieving token.'],
 
242
            ['INFO Retrieving token.'],
280
243
            self.log_stream.getvalue().splitlines())
281
244
 
282
245
    def test_check_token_not_found_juju_ssh_broken(self):
283
246
        env = JujuData('foo', {'type': 'local'})
284
247
        client = EnvJujuClient(env, None, None)
285
 
        status = Status.from_text("""\
286
 
            applications:
287
 
              dummy-sink:
288
 
                units:
289
 
                  dummy-sink/0:
290
 
                    workload-status:
291
 
                      current: active
292
 
                      message: Token is token
293
 
 
294
 
            """)
295
248
        remote = SSHRemote(client, 'unit', None, series='xenial')
296
249
        with patch('deploy_stack.remote_from_unit', autospec=True,
297
250
                   return_value=remote):
299
252
                              side_effect=['', 'token']) as rr_mock:
300
253
                with patch.object(remote, 'get_address',
301
254
                                  autospec=True) as ga_mock:
302
 
                    with patch.object(client, 'get_status', autospec=True,
303
 
                                      return_value=status):
304
 
                        with self.assertRaisesRegexp(ValueError,
305
 
                                                     "Token is 'token'"):
306
 
                            check_token(client, 'token', timeout=0)
 
255
                    with self.assertRaisesRegexp(ValueError,
 
256
                                                 "Token is 'token'"):
 
257
                        check_token(client, 'token', timeout=0)
307
258
        self.assertEqual(2, rr_mock.call_count)
308
259
        rr_mock.assert_called_with(GET_TOKEN_SCRIPT)
309
260
        ga_mock.assert_called_once_with()
310
261
        self.assertFalse(remote.use_juju_ssh)
311
262
        self.assertEqual(
312
 
            ['INFO Waiting for applications to reach ready.',
313
 
             'INFO Retrieving token.',
 
263
            ['INFO Retrieving token.',
314
264
             "INFO Token matches expected 'token'",
315
265
             'ERROR juju ssh to unit is broken.'],
316
266
            self.log_stream.getvalue().splitlines())
317
267
 
318
 
    def test_check_token_win_status(self):
319
 
        env = JujuData('foo', {'type': 'azure'})
320
 
        client = EnvJujuClient(env, None, None)
321
 
        remote = MagicMock(spec=['cat', 'is_windows'])
322
 
        remote.is_windows.return_value = True
323
 
        status = Status.from_text("""\
324
 
            applications:
325
 
              dummy-sink:
326
 
                units:
327
 
                  dummy-sink/0:
328
 
                    workload-status:
329
 
                      current: active
330
 
                      message: Token is token
331
 
 
332
 
            """)
333
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
334
 
                   return_value=remote):
335
 
            with patch.object(client, 'get_status', autospec=True,
336
 
                              return_value=status):
337
 
                check_token(client, 'token', timeout=0)
338
 
        # application-status had the token.
339
 
        self.assertEqual(0, remote.cat.call_count)
340
 
        self.assertEqual(
341
 
            ['INFO Waiting for applications to reach ready.',
342
 
             'INFO Retrieving token.',
343
 
             "INFO Token matches expected 'token'"],
344
 
            self.log_stream.getvalue().splitlines())
345
 
 
346
 
    def test_check_token_win_remote(self):
347
 
        env = JujuData('foo', {'type': 'azure'})
348
 
        client = EnvJujuClient(env, None, None)
349
 
        remote = MagicMock(spec=['cat', 'is_windows'])
350
 
        remote.is_windows.return_value = True
351
 
        remote.cat.return_value = 'token'
352
 
        status = Status.from_text("""\
353
 
            applications:
354
 
              dummy-sink:
355
 
                units:
356
 
                  dummy-sink/0:
357
 
                    juju-status:
358
 
                      current: active
359
 
            """)
360
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
361
 
                   return_value=remote):
362
 
            with patch.object(client, 'get_status', autospec=True,
363
 
                              return_value=status):
364
 
                check_token(client, 'token', timeout=0)
365
 
        # application-status did not have the token, winrm did.
366
 
        remote.cat.assert_called_once_with('%ProgramData%\\dummy-sink\\token')
367
 
        self.assertEqual(
368
 
            ['INFO Waiting for applications to reach ready.',
369
 
             'INFO Retrieving token.',
370
 
             "INFO Token matches expected 'token'"],
371
 
            self.log_stream.getvalue().splitlines())
372
 
 
373
 
    def test_check_token_win_remote_failure(self):
374
 
        env = JujuData('foo', {'type': 'azure'})
375
 
        client = EnvJujuClient(env, None, None)
376
 
        remote = MagicMock(spec=['cat', 'is_windows'])
377
 
        remote.is_windows.return_value = True
378
 
        remote.cat.side_effect = winrm.exceptions.WinRMTransportError(
379
 
            'a', 'oops')
380
 
        status = Status.from_text("""\
381
 
            applications:
382
 
              dummy-sink:
383
 
                units:
384
 
                  dummy-sink/0:
385
 
                    juju-status:
386
 
                      current: active
387
 
            """)
388
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
389
 
                   return_value=remote):
390
 
            with patch.object(client, 'get_status', autospec=True,
391
 
                              return_value=status):
392
 
                check_token(client, 'token', timeout=0)
393
 
        # application-status did not have the token, winrm did.
394
 
        remote.cat.assert_called_once_with('%ProgramData%\\dummy-sink\\token')
395
 
        self.assertEqual(
396
 
            ['INFO Waiting for applications to reach ready.',
397
 
             'INFO Retrieving token.',
398
 
             'WARNING Skipping token check because of: '
399
 
                '500 WinRMTransport. oops'],
400
 
            self.log_stream.getvalue().splitlines())
401
 
 
402
268
    log_level = logging.DEBUG
403
269
 
404
270
 
499
365
            log_path = os.path.join(log_dir, 'fake.log')
500
366
            cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path])
501
367
 
502
 
    def test_archive_logs_syslog(self):
503
 
        with temp_dir() as log_dir:
504
 
            log_path = os.path.join(log_dir, 'syslog')
505
 
            with open(log_path, 'w') as f:
506
 
                f.write('syslog contents')
507
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
508
 
                archive_logs(log_dir)
509
 
            cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path])
510
 
 
511
368
    def test_archive_logs_subdir(self):
512
369
        with temp_dir() as log_dir:
513
370
            subdir = os.path.join(log_dir, "subdir")
525
382
                archive_logs(log_dir)
526
383
        self.assertEquals(cc_mock.call_count, 0)
527
384
 
528
 
    def test_archive_logs_multiple(self):
529
 
        with temp_dir() as log_dir:
530
 
            log_paths = []
531
 
            with open(os.path.join(log_dir, 'fake.log'), 'w') as f:
532
 
                f.write('log contents')
533
 
            log_paths.append(os.path.join(log_dir, 'fake.log'))
534
 
            subdir = os.path.join(log_dir, "subdir")
535
 
            os.mkdir(subdir)
536
 
            with open(os.path.join(subdir, 'syslog'), 'w') as f:
537
 
                f.write('syslog contents')
538
 
            log_paths.append(os.path.join(subdir, 'syslog'))
539
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
540
 
                archive_logs(log_dir)
541
 
            self.assertEqual(1, cc_mock.call_count)
542
 
            call_args, call_kwargs = cc_mock.call_args
543
 
            gzip_args = call_args[0]
544
 
            self.assertEqual(0, len(call_kwargs))
545
 
            self.assertEqual(gzip_args[:3], ['gzip', '--best', '-f'])
546
 
            self.assertEqual(set(gzip_args[3:]), set(log_paths))
547
 
 
548
385
    def test_copy_local_logs(self):
549
386
        # Relevent local log files are copied, after changing their permissions
550
387
        # to allow access by non-root user.
832
669
                               autospec=True) as ct_mock:
833
670
                        assess_juju_relations(client)
834
671
        assert_juju_call(self, cc_mock, client, (
835
 
            'juju', '--show-log', 'config', '-m', 'foo:foo',
 
672
            'juju', '--show-log', 'set-config', '-m', 'foo:foo',
836
673
            'dummy-source', 'token=fake-token'), 0)
837
674
        ct_mock.assert_called_once_with(client, 'fake-token')
838
675
 
899
736
        self.assertEqual(cc_mock.call_count, 4)
900
737
        self.assertEqual(
901
738
            [
902
 
                call('show-status', '--format', 'yaml', controller=False)
 
739
                call('show-status', '--format', 'yaml', admin=False)
903
740
            ],
904
741
            gjo_mock.call_args_list)
905
742
 
999
836
    def ds_cxt(self):
1000
837
        env = JujuData('foo', {})
1001
838
        client = fake_EnvJujuClient(env)
1002
 
        bc_cxt = patch('deploy_stack.client_from_config',
 
839
        bc_cxt = patch('jujupy.EnvJujuClient.by_version',
1003
840
                       return_value=client)
1004
841
        fc_cxt = patch('jujupy.SimpleEnvironment.from_config',
1005
842
                       return_value=env)
1006
843
        mgr = MagicMock()
1007
844
        bm_cxt = patch('deploy_stack.BootstrapManager', autospec=True,
1008
845
                       return_value=mgr)
1009
 
        juju_cxt = patch('jujupy.EnvJujuClient.juju', autospec=True)
 
846
        juju_cxt = patch('deploy_stack.EnvJujuClient.juju', autospec=True)
1010
847
        ajr_cxt = patch('deploy_stack.assess_juju_run', autospec=True)
1011
848
        dds_cxt = patch('deploy_stack.deploy_dummy_stack', autospec=True)
1012
849
        with bc_cxt, fc_cxt, bm_cxt as bm_mock, juju_cxt, ajr_cxt, dds_cxt:
1020
857
            charm_prefix=None, bootstrap_host=None, machine=None,
1021
858
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1022
859
            keep_env=False, upload_tools=False, with_chaos=1, jes=False,
1023
 
            region=None, verbose=False, upgrade=False, deadline=None,
 
860
            region=None, verbose=False, upgrade=False,
1024
861
        )
1025
862
        with self.ds_cxt():
1026
863
            with patch('deploy_stack.background_chaos',
1043
880
            charm_prefix=None, bootstrap_host=None, machine=None,
1044
881
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1045
882
            keep_env=False, upload_tools=False, with_chaos=0, jes=False,
1046
 
            region=None, verbose=False, upgrade=False, deadline=None,
 
883
            region=None, verbose=False, upgrade=False,
1047
884
        )
1048
885
        with self.ds_cxt():
1049
886
            with patch('deploy_stack.background_chaos',
1061
898
            charm_prefix=None, bootstrap_host=None, machine=None,
1062
899
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1063
900
            keep_env=False, upload_tools=False, with_chaos=0, jes=False,
1064
 
            region='region-foo', verbose=False, upgrade=False, deadline=None,
 
901
            region='region-foo', verbose=False, upgrade=False,
1065
902
        )
1066
903
        with self.ds_cxt() as (client, bm_mock):
1067
904
            with patch('deploy_stack.assess_juju_relations',
1109
946
    STATUS = (
1110
947
        'juju', '--show-log', 'show-status', '-m', 'foo:foo',
1111
948
        '--format', 'yaml')
1112
 
    CONTROLLER_STATUS = (
1113
 
        'juju', '--show-log', 'show-status', '-m', 'foo:controller',
1114
 
        '--format', 'yaml')
1115
 
    GET_ENV = ('juju', '--show-log', 'model-config', '-m', 'foo:foo',
1116
 
               'agent-metadata-url')
1117
 
    GET_CONTROLLER_ENV = (
1118
 
        'juju', '--show-log', 'model-config', '-m', 'foo:controller',
1119
 
        'agent-metadata-url')
1120
 
    LIST_MODELS = (
1121
 
        'juju', '--show-log', 'list-models', '-c', 'foo', '--format', 'yaml')
 
949
    GET_ENV = ('juju', '--show-log', 'get-model-config', '-m', 'foo:foo',
 
950
               'tools-metadata-url')
1122
951
 
1123
952
    @classmethod
1124
953
    def upgrade_output(cls, args, **kwargs):
1125
954
        status = yaml.safe_dump({
1126
955
            'machines': {'0': {
1127
956
                'agent-state': 'started',
1128
 
                'agent-version': '2.0-rc2'}},
 
957
                'agent-version': '2.0-alpha3'}},
1129
958
            'services': {}})
1130
959
        juju_run_out = json.dumps([
1131
960
            {"MachineId": "1", "Stdout": "Linux\n"},
1132
961
            {"MachineId": "2", "Stdout": "Linux\n"}])
1133
 
        list_models = json.dumps(
1134
 
            {'models': [
1135
 
                {'name': 'controller'},
1136
 
                {'name': 'foo'},
1137
 
            ]})
1138
962
        output = {
1139
963
            cls.STATUS: status,
1140
 
            cls.CONTROLLER_STATUS: status,
1141
964
            cls.RUN_UNAME: juju_run_out,
1142
 
            cls.GET_ENV: 'testing',
1143
 
            cls.GET_CONTROLLER_ENV: 'testing',
1144
 
            cls.LIST_MODELS: list_models,
 
965
            cls.GET_ENV: 'testing'
1145
966
        }
1146
967
        return FakePopen(output[args], '', 0)
1147
968
 
1155
976
                               return_value="FAKETOKEN", autospec=True):
1156
977
                        with patch('jujupy.EnvJujuClient.get_version',
1157
978
                                   side_effect=lambda cls:
1158
 
                                   '2.0-rc2-arch-series'):
1159
 
                            with patch(
1160
 
                                    'jujupy.get_timeout_prefix',
1161
 
                                    autospec=True, return_value=()):
1162
 
                                yield (co_mock, cc_mock)
 
979
                                   '2.0-alpha3-arch-series'):
 
980
                            yield (co_mock, cc_mock)
1163
981
 
1164
982
    def test_assess_upgrade(self):
1165
983
        env = JujuData('foo', {'type': 'foo'})
1167
985
        with self.upgrade_mocks() as (co_mock, cc_mock):
1168
986
            assess_upgrade(old_client, '/bar/juju')
1169
987
        new_client = EnvJujuClient(env, None, '/bar/juju')
1170
 
        # Needs to upgrade the controller first.
1171
 
        assert_juju_call(self, cc_mock, new_client, (
1172
 
            'juju', '--show-log', 'upgrade-juju', '-m', 'foo:controller',
1173
 
            '--agent-version', '2.0-rc2'), 0)
1174
 
        assert_juju_call(self, cc_mock, new_client, (
1175
 
            'juju', '--show-log', 'upgrade-juju', '-m', 'foo:foo',
1176
 
            '--agent-version', '2.0-rc2'), 1)
1177
 
        self.assertEqual(cc_mock.call_count, 2)
1178
 
        assert_juju_call(self, co_mock, new_client, self.LIST_MODELS, 0)
1179
 
        assert_juju_call(self, co_mock, new_client, self.GET_CONTROLLER_ENV, 1)
1180
 
        assert_juju_call(self, co_mock, new_client, self.GET_CONTROLLER_ENV, 2)
1181
 
        assert_juju_call(self, co_mock, new_client, self.CONTROLLER_STATUS, 3)
1182
 
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 4)
1183
 
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 5)
1184
 
        assert_juju_call(self, co_mock, new_client, self.STATUS, 6)
1185
 
        self.assertEqual(co_mock.call_count, 7)
1186
 
 
1187
 
    def test__get_clients_to_upgrade_returns_new_version_class(self):
1188
 
        env = SimpleEnvironment('foo', {'type': 'foo'})
1189
 
        old_client = fake_juju_client(
1190
 
            env, '/foo/juju', version='1.25', cls=EnvJujuClient25)
1191
 
        with patch('jujupy.EnvJujuClient.get_version',
1192
 
                   return_value='1.26-arch-series'):
1193
 
            with patch('jujupy.EnvJujuClient26._get_models', return_value=[]):
1194
 
                [new_client] = _get_clients_to_upgrade(
1195
 
                    old_client, '/foo/newer/juju')
1196
 
 
1197
 
        self.assertIs(type(new_client), EnvJujuClient26)
1198
 
 
1199
 
    def test__get_clients_to_upgrade_returns_controller_and_model(self):
1200
 
        old_client = fake_juju_client()
1201
 
        old_client.bootstrap()
1202
 
 
1203
 
        with patch('jujupy.EnvJujuClient.get_version',
1204
 
                   return_value='2.0-rc2-arch-series'):
1205
 
            new_clients = _get_clients_to_upgrade(
1206
 
                old_client, '/foo/newer/juju')
1207
 
 
1208
 
        self.assertEqual(len(new_clients), 2)
1209
 
        self.assertEqual(new_clients[0].model_name, 'controller')
1210
 
        self.assertEqual(new_clients[1].model_name, 'name')
 
988
        assert_juju_call(self, cc_mock, new_client, (
 
989
            'juju', '--show-log', 'upgrade-juju', '-m', 'foo:foo', '--version',
 
990
            '2.0-alpha3'), 0)
 
991
        self.assertEqual(cc_mock.call_count, 1)
 
992
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 0)
 
993
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 1)
 
994
        assert_juju_call(self, co_mock, new_client, self.STATUS, 2)
 
995
        self.assertEqual(co_mock.call_count, 3)
1211
996
 
1212
997
    def test_mass_timeout(self):
1213
998
        config = {'type': 'foo'}
1215
1000
        with self.upgrade_mocks():
1216
1001
            with patch.object(EnvJujuClient, 'wait_for_version') as wfv_mock:
1217
1002
                assess_upgrade(old_client, '/bar/juju')
1218
 
            wfv_mock.assert_has_calls([call('2.0-rc2', 600)] * 2)
 
1003
            wfv_mock.assert_called_once_with('2.0-alpha3', 600)
1219
1004
            config['type'] = 'maas'
1220
1005
            with patch.object(EnvJujuClient, 'wait_for_version') as wfv_mock:
1221
1006
                assess_upgrade(old_client, '/bar/juju')
1222
 
        wfv_mock.assert_has_calls([call('2.0-rc2', 1200)] * 2)
 
1007
        wfv_mock.assert_called_once_with('2.0-alpha3', 1200)
1223
1008
 
1224
1009
 
1225
1010
class TestBootstrapManager(FakeHomeTestCase):
1226
1011
 
1227
1012
    def test_from_args(self):
1228
 
        deadline = datetime(2012, 11, 10, 9, 8, 7)
1229
1013
        args = Namespace(
1230
1014
            env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
1231
1015
            bootstrap_host='example.org', machine=['example.com'],
1232
1016
            series='angsty', agent_url='qux', agent_stream='escaped',
1233
 
            region='eu-west-northwest-5', logs='pine', keep_env=True,
1234
 
            deadline=deadline)
1235
 
        with patch('deploy_stack.client_from_config') as fc_mock:
1236
 
            bs_manager = BootstrapManager.from_args(args)
1237
 
        fc_mock.assert_called_once_with('foo', 'bar', debug=True,
1238
 
                                        soft_deadline=deadline)
 
1017
            region='eu-west-northwest-5', logs='pine', keep_env=True)
 
1018
        with patch.object(SimpleEnvironment, 'from_config') as fc_mock:
 
1019
            with patch.object(EnvJujuClient, 'by_version') as bv_mock:
 
1020
                bs_manager = BootstrapManager.from_args(args)
 
1021
        fc_mock.assert_called_once_with('foo')
 
1022
        bv_mock.assert_called_once_with(fc_mock.return_value, 'bar',
 
1023
                                        debug=True)
1239
1024
        self.assertEqual('baz', bs_manager.temp_env_name)
1240
 
        self.assertIs(fc_mock.return_value, bs_manager.client)
1241
 
        self.assertIs(fc_mock.return_value, bs_manager.tear_down_client)
 
1025
        self.assertIs(bv_mock.return_value, bs_manager.client)
 
1026
        self.assertIs(bv_mock.return_value, bs_manager.tear_down_client)
1242
1027
        self.assertEqual('example.org', bs_manager.bootstrap_host)
1243
1028
        self.assertEqual(['example.com'], bs_manager.machines)
1244
1029
        self.assertEqual('angsty', bs_manager.series)
1252
1037
        self.assertEqual(jes_enabled, bs_manager.jes_enabled)
1253
1038
        self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts)
1254
1039
 
1255
 
    def test_no_args(self):
1256
 
        args = Namespace(
1257
 
            env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
1258
 
            bootstrap_host='example.org', machine=['example.com'],
1259
 
            series='angsty', agent_url='qux', agent_stream='escaped',
1260
 
            region='eu-west-northwest-5', logs=None, keep_env=True,
1261
 
            deadline=None)
1262
 
        with patch('deploy_stack.client_from_config') as fc_mock:
1263
 
            with patch('utility.os.makedirs'):
1264
 
                bs_manager = BootstrapManager.from_args(args)
1265
 
        fc_mock.assert_called_once_with('foo', 'bar', debug=True,
1266
 
                                        soft_deadline=None)
1267
 
        self.assertEqual('baz', bs_manager.temp_env_name)
1268
 
        self.assertIs(fc_mock.return_value, bs_manager.client)
1269
 
        self.assertIs(fc_mock.return_value, bs_manager.tear_down_client)
1270
 
        self.assertEqual('example.org', bs_manager.bootstrap_host)
1271
 
        self.assertEqual(['example.com'], bs_manager.machines)
1272
 
        self.assertEqual('angsty', bs_manager.series)
1273
 
        self.assertEqual('qux', bs_manager.agent_url)
1274
 
        self.assertEqual('escaped', bs_manager.agent_stream)
1275
 
        self.assertEqual('eu-west-northwest-5', bs_manager.region)
1276
 
        self.assertIs(True, bs_manager.keep_env)
1277
 
        logs_arg = bs_manager.log_dir.split("/")
1278
 
        logs_ts = logs_arg[4]
1279
 
        self.assertEqual(logs_arg[1:4], ['tmp', 'baz', 'logs'])
1280
 
        self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S"))
1281
 
        jes_enabled = bs_manager.client.is_jes_enabled.return_value
1282
 
        self.assertEqual(jes_enabled, bs_manager.permanent)
1283
 
        self.assertEqual(jes_enabled, bs_manager.jes_enabled)
1284
 
        self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts)
1285
 
 
1286
1040
    def test_jes_not_permanent(self):
1287
1041
        with self.assertRaisesRegexp(ValueError, 'Cannot set permanent False'
1288
1042
                                     ' if jes_enabled is True.'):
1310
1064
            env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
1311
1065
            bootstrap_host=None, machine=['example.com'],
1312
1066
            series='angsty', agent_url='qux', agent_stream='escaped',
1313
 
            region='eu-west-northwest-5', logs='pine', keep_env=True,
1314
 
            deadline=None)
1315
 
        with patch('deploy_stack.client_from_config'):
1316
 
            bs_manager = BootstrapManager.from_args(args)
 
1067
            region='eu-west-northwest-5', logs='pine', keep_env=True)
 
1068
        with patch.object(SimpleEnvironment, 'from_config'):
 
1069
            with patch.object(EnvJujuClient, 'by_version'):
 
1070
                bs_manager = BootstrapManager.from_args(args)
1317
1071
        self.assertIs(None, bs_manager.bootstrap_host)
1318
1072
        self.assertEqual({}, bs_manager.known_hosts)
1319
1073
 
1470
1224
        wfp_mock.assert_called_once_with(
1471
1225
            'bootstrap.example.org', 22, timeout=120)
1472
1226
 
1473
 
    def test_handle_bootstrap_exceptions_ignores_soft_deadline(self):
1474
 
        env = JujuData('foo', {'type': 'nonlocal'})
1475
 
        client = EnvJujuClient(env, None, None)
1476
 
        tear_down_client = EnvJujuClient(env, None, None)
1477
 
        soft_deadline = datetime(2015, 1, 2, 3, 4, 6)
1478
 
        now = soft_deadline + timedelta(seconds=1)
1479
 
        client.env.juju_home = use_context(self, temp_dir())
1480
 
        bs_manager = BootstrapManager(
1481
 
            'foobar', client, tear_down_client, None, [], None, None, None,
1482
 
            None, client.env.juju_home, False, permanent=True,
1483
 
            jes_enabled=True)
1484
 
 
1485
 
        def do_check(*args, **kwargs):
1486
 
            with client.check_timeouts():
1487
 
                with tear_down_client.check_timeouts():
1488
 
                    pass
1489
 
 
1490
 
        with patch.object(bs_manager.tear_down_client, 'juju',
1491
 
                          side_effect=do_check, autospec=True):
1492
 
            with patch.object(client._backend, '_now', return_value=now):
1493
 
                fake_exception = Exception()
1494
 
                with self.assertRaises(LoggedException) as exc:
1495
 
                    with bs_manager.handle_bootstrap_exceptions():
1496
 
                        client._backend.soft_deadline = soft_deadline
1497
 
                        tear_down_client._backend.soft_deadline = soft_deadline
1498
 
                        raise fake_exception
1499
 
                self.assertIs(fake_exception, exc.exception.exception)
1500
 
 
1501
1227
    def test_tear_down_requires_same_env(self):
1502
1228
        client = self.make_client()
1503
1229
        client.env.juju_home = 'foobar'
1596
1322
                '2': 'example.org',
1597
1323
                })
1598
1324
 
1599
 
    def test_runtime_context_raises_logged_exception(self):
1600
 
        client = fake_juju_client()
1601
 
        client.bootstrap()
1602
 
        bs_manager = BootstrapManager(
1603
 
                'foobar', client, client,
1604
 
                None, [], None, None, None, None, client.env.juju_home, False,
1605
 
                True, True)
1606
 
        test_error = Exception("Some exception")
1607
 
        test_error.output = "a stdout value"
1608
 
        test_error.stderr = "a stderr value"
1609
 
        with patch.object(bs_manager, 'dump_all_logs', autospec=True):
1610
 
            with self.assertRaises(LoggedException) as err_ctx:
1611
 
                with bs_manager.runtime_context([]):
1612
 
                    raise test_error
1613
 
                self.assertIs(err_ctx.exception.exception, test_error)
1614
 
        self.assertIn("a stdout value", self.log_stream.getvalue())
1615
 
        self.assertIn("a stderr value", self.log_stream.getvalue())
1616
 
 
1617
1325
    def test_runtime_context_looks_up_host(self):
1618
1326
        client = fake_juju_client()
1619
1327
        client.bootstrap()
1702
1410
        wfp_mock.assert_called_once_with(
1703
1411
            'bootstrap.example.org', 22, timeout=120)
1704
1412
 
1705
 
    @contextmanager
1706
 
    def booted_to_bootstrap(self, bs_manager):
1707
 
        """Preform patches to focus on the call to bootstrap."""
1708
 
        with patch.object(bs_manager, 'dump_all_logs'):
1709
 
            with patch.object(bs_manager, 'runtime_context'):
1710
 
                with patch.object(bs_manager.client, 'juju'):
1711
 
                    with patch.object(bs_manager.client, 'bootstrap') as mock:
1712
 
                        yield mock
1713
 
 
1714
 
    def test_booted_context_kwargs(self):
1715
 
        client = fake_juju_client()
1716
 
        with temp_dir() as root:
1717
 
            log_dir = os.path.join(root, 'log-dir')
1718
 
            os.mkdir(log_dir)
1719
 
            bs_manager = BootstrapManager(
1720
 
                'foobar', client, client,
1721
 
                None, [], None, None, None, None, log_dir, False,
1722
 
                True, True)
1723
 
            juju_home = os.path.join(root, 'juju-home')
1724
 
            os.mkdir(juju_home)
1725
 
            client.env.juju_home = juju_home
1726
 
            with self.booted_to_bootstrap(bs_manager) as bootstrap_mock:
1727
 
                with bs_manager.booted_context(False, to='test'):
1728
 
                    bootstrap_mock.assert_called_once_with(
1729
 
                        upload_tools=False, to='test', bootstrap_series=None)
1730
 
            with self.booted_to_bootstrap(bs_manager) as bootstrap_mock:
1731
 
                with bs_manager.existing_booted_context(False, to='test'):
1732
 
                    bootstrap_mock.assert_called_once_with(
1733
 
                        upload_tools=False, to='test', bootstrap_series=None)
1734
 
 
1735
 
    def test_runtime_context_teardown_ignores_soft_deadline(self):
1736
 
        env = JujuData('foo', {'type': 'nonlocal'})
1737
 
        soft_deadline = datetime(2015, 1, 2, 3, 4, 6)
1738
 
        now = soft_deadline + timedelta(seconds=1)
1739
 
        client = EnvJujuClient(env, None, None)
1740
 
        tear_down_client = EnvJujuClient(env, None, None)
1741
 
 
1742
 
        def do_check_client(*args, **kwargs):
1743
 
            with client.check_timeouts():
1744
 
                return iter([])
1745
 
 
1746
 
        def do_check_teardown_client(*args, **kwargs):
1747
 
            with tear_down_client.check_timeouts():
1748
 
                return iter([])
1749
 
 
1750
 
        with temp_dir() as log_dir:
1751
 
            bs_manager = BootstrapManager(
1752
 
                'foobar', client, tear_down_client,
1753
 
                None, [], None, None, None, None, log_dir, False,
1754
 
                True, True)
1755
 
            bs_manager.known_hosts['0'] = 'example.org'
1756
 
            with patch.object(bs_manager.client, 'juju',
1757
 
                              side_effect=do_check_client, autospec=True):
1758
 
                with patch.object(bs_manager.client, 'iter_model_clients',
1759
 
                                  side_effect=do_check_client, autospec=True,
1760
 
                                  ):
1761
 
                    with patch.object(bs_manager, 'tear_down',
1762
 
                                      do_check_teardown_client):
1763
 
                        with patch.object(client._backend, '_now',
1764
 
                                          return_value=now):
1765
 
                            with bs_manager.runtime_context(['baz']):
1766
 
                                client._backend.soft_deadline = soft_deadline
1767
 
                                td_backend = tear_down_client._backend
1768
 
                                td_backend.soft_deadline = soft_deadline
1769
 
 
1770
 
    @contextmanager
1771
 
    def make_bootstrap_manager(self):
1772
 
        client = fake_juju_client()
1773
 
        with temp_dir() as log_dir:
1774
 
            bs_manager = BootstrapManager(
1775
 
                'foobar', client, client,
1776
 
                None, [], None, None, None, None, log_dir, False,
1777
 
                True, True)
1778
 
            yield bs_manager
1779
 
 
1780
 
    def test_top_context_dumps_timings(self):
1781
 
        with self.make_bootstrap_manager() as bs_manager:
1782
 
            with patch('deploy_stack.dump_juju_timings') as djt_mock:
1783
 
                with bs_manager.top_context():
1784
 
                    pass
1785
 
        djt_mock.assert_called_once_with(bs_manager.client, bs_manager.log_dir)
1786
 
 
1787
 
    def test_top_context_dumps_timings_on_exception(self):
1788
 
        with self.make_bootstrap_manager() as bs_manager:
1789
 
            with patch('deploy_stack.dump_juju_timings') as djt_mock:
1790
 
                with self.assertRaises(ValueError):
1791
 
                    with bs_manager.top_context():
1792
 
                        raise ValueError
1793
 
        djt_mock.assert_called_once_with(bs_manager.client, bs_manager.log_dir)
1794
 
 
1795
 
    def test_top_context_no_log_dir_skips_timings(self):
1796
 
        with self.make_bootstrap_manager() as bs_manager:
1797
 
            bs_manager.log_dir = None
1798
 
            with patch('deploy_stack.dump_juju_timings') as djt_mock:
1799
 
                with bs_manager.top_context():
1800
 
                    pass
1801
 
        self.assertEqual(djt_mock.call_count, 0)
1802
 
 
1803
1413
 
1804
1414
class TestBootContext(FakeHomeTestCase):
1805
1415
 
2196
1806
            with_chaos=0,
2197
1807
            jes=False,
2198
1808
            region=None,
2199
 
            deadline=None,
2200
1809
        ))
2201
1810
 
2202
1811
    def test_upload_tools(self):