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

« back to all changes in this revision

Viewing changes to test_deploy_stack.py

  • Committer: Aaron Bentley
  • Date: 2015-08-19 15:07:08 UTC
  • mto: This revision was merged to the branch mainline in revision 1069.
  • Revision ID: aaron.bentley@canonical.com-20150819150708-88xesx4iardg12b4
Wait for proc to exit after signalling.

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
    TestCase
 
13
)
17
14
 
18
15
from mock import (
19
16
    call,
20
 
    MagicMock,
21
17
    patch,
22
 
    )
 
18
)
23
19
import yaml
24
20
 
25
21
from deploy_stack import (
26
22
    archive_logs,
27
 
    assess_juju_relations,
28
23
    assess_juju_run,
29
 
    assess_upgrade,
30
24
    boot_context,
31
 
    BootstrapManager,
32
 
    check_token,
33
25
    copy_local_logs,
34
26
    copy_remote_logs,
35
27
    deploy_dummy_stack,
36
 
    deploy_job,
37
28
    _deploy_job,
38
29
    deploy_job_parse_args,
39
30
    destroy_environment,
40
31
    dump_env_logs,
41
32
    dump_juju_timings,
42
 
    _get_clients_to_upgrade,
43
33
    iter_remote_machines,
44
34
    get_remote_machines,
45
35
    GET_TOKEN_SCRIPT,
 
36
    assess_upgrade,
46
37
    safe_print_status,
47
38
    retain_config,
48
39
    update_env,
49
 
    )
50
 
from fakejuju import (
51
 
    fake_juju_client,
52
 
    fake_juju_client_optional_jes,
53
 
    )
54
 
from jujuconfig import (
55
 
    get_environments_path,
56
 
    get_jenv_path,
57
 
    get_juju_home,
58
 
    )
 
40
)
59
41
from jujupy import (
60
42
    EnvJujuClient,
61
 
    EnvJujuClient1X,
62
 
    EnvJujuClient25,
63
 
    EnvJujuClient26,
64
 
    get_cache_path,
65
 
    get_timeout_prefix,
66
 
    get_timeout_path,
67
 
    JujuData,
68
 
    KILL_CONTROLLER,
69
43
    SimpleEnvironment,
70
44
    Status,
71
 
    )
 
45
)
72
46
from remote import (
73
47
    _Remote,
74
48
    remote_from_address,
75
 
    SSHRemote,
76
 
    winrm,
77
 
    )
78
 
from tests import (
79
 
    FakeHomeTestCase,
80
 
    temp_os_env,
81
 
    use_context,
82
 
    )
83
 
from tests.test_jujupy import (
 
49
)
 
50
from test_jujupy import (
84
51
    assert_juju_call,
85
 
    FakePopen,
86
 
    observable_temp_file,
87
 
    )
 
52
)
88
53
from utility import (
89
 
    LoggedException,
 
54
    setup_test_logging,
90
55
    temp_dir,
91
 
    )
 
56
)
92
57
 
93
58
 
94
59
def make_logs(log_dir):
100
65
    return write_dumped_files
101
66
 
102
67
 
103
 
class DeployStackTestCase(FakeHomeTestCase):
 
68
class DeployStackTestCase(TestCase):
 
69
 
 
70
    def setUp(self):
 
71
        setup_test_logging(self)
104
72
 
105
73
    def test_destroy_environment(self):
106
 
        client = EnvJujuClient1X(
 
74
        client = EnvJujuClient(
107
75
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
108
76
        with patch.object(client,
109
77
                          'destroy_environment', autospec=True) as de_mock:
114
82
        self.assertEqual(0, dji_mock.call_count)
115
83
 
116
84
    def test_destroy_environment_with_manual_type_aws(self):
117
 
        os.environ['AWS_ACCESS_KEY'] = 'fake-juju-ci-testing-key'
118
 
        client = EnvJujuClient1X(
 
85
        client = EnvJujuClient(
119
86
            SimpleEnvironment('foo', {'type': 'manual'}), '1.234-76', None)
120
87
        with patch.object(client,
121
88
                          'destroy_environment', autospec=True) as de_mock:
122
89
            with patch('deploy_stack.destroy_job_instances',
123
90
                       autospec=True) as dji_mock:
124
 
                destroy_environment(client, 'foo')
 
91
                with patch.dict(os.environ, {'AWS_ACCESS_KEY': 'bar'}):
 
92
                    destroy_environment(client, 'foo')
125
93
        self.assertEqual(1, de_mock.call_count)
126
 
        dji_mock.assert_called_once_with('foo')
 
94
        dji_mock.assert_called_with('foo')
127
95
 
128
96
    def test_destroy_environment_with_manual_type_non_aws(self):
129
 
        client = EnvJujuClient1X(
 
97
        client = EnvJujuClient(
130
98
            SimpleEnvironment('foo', {'type': 'manual'}), '1.234-76', None)
131
99
        with patch.object(client,
132
100
                          'destroy_environment', autospec=True) as de_mock:
133
101
            with patch('deploy_stack.destroy_job_instances',
134
102
                       autospec=True) as dji_mock:
135
103
                destroy_environment(client, 'foo')
136
 
        self.assertEqual(os.environ.get('AWS_ACCESS_KEY'), None)
137
104
        self.assertEqual(1, de_mock.call_count)
138
105
        self.assertEqual(0, dji_mock.call_count)
139
106
 
140
107
    def test_assess_juju_run(self):
141
 
        env = JujuData('foo', {'type': 'nonlocal'})
 
108
        env = SimpleEnvironment('foo', {'type': 'nonlocal'})
142
109
        client = EnvJujuClient(env, None, None)
143
110
        response_ok = json.dumps(
144
111
            [{"MachineId": "1", "Stdout": "Linux\n"},
150
117
             "ReturnCode": 255,
151
118
             "Stderr": "Permission denied (publickey,password)"}])
152
119
        with patch.object(client, 'get_juju_output', autospec=True,
153
 
                          return_value=response_ok) as gjo_mock:
 
120
                          return_value=response_ok):
154
121
            responses = assess_juju_run(client)
155
122
            for machine in responses:
156
123
                self.assertFalse(machine.get('ReturnCode', False))
157
124
                self.assertIn(machine.get('MachineId'), ["1", "2"])
158
125
            self.assertEqual(len(responses), 2)
159
 
        gjo_mock.assert_called_once_with(
160
 
            'run', '--format', 'json', '--application',
161
 
            'dummy-source,dummy-sink', 'uname')
162
126
        with patch.object(client, 'get_juju_output', autospec=True,
163
 
                          return_value=response_err) as gjo_mock:
 
127
                          return_value=response_err):
164
128
            with self.assertRaises(ValueError):
165
129
                responses = assess_juju_run(client)
166
 
        gjo_mock.assert_called_once_with(
167
 
            'run', '--format', 'json', '--application',
168
 
            'dummy-source,dummy-sink', 'uname')
169
130
 
170
131
    def test_safe_print_status(self):
171
 
        env = JujuData('foo', {'type': 'nonlocal'})
 
132
        env = SimpleEnvironment('foo', {'type': 'nonlocal'})
172
133
        client = EnvJujuClient(env, None, None)
173
 
        error = subprocess.CalledProcessError(1, 'status', 'status error')
174
 
        with patch.object(client, 'juju', autospec=True,
175
 
                          side_effect=[error]) as mock:
176
 
            with patch.object(client, 'iter_model_clients',
177
 
                              return_value=[client]) as imc_mock:
178
 
                safe_print_status(client)
179
 
        mock.assert_called_once_with('show-status', ('--format', 'yaml'))
180
 
        imc_mock.assert_called_once_with()
 
134
        with patch.object(
 
135
                client, 'juju', autospec=True,
 
136
                side_effect=subprocess.CalledProcessError(
 
137
                    1, 'status', 'status error')
 
138
        ) as mock:
 
139
            safe_print_status(client)
 
140
        mock.assert_called_once_with('status', ())
181
141
 
182
142
    def test_update_env(self):
183
143
        env = SimpleEnvironment('foo', {'type': 'paas'})
190
150
        self.assertEqual('baz', env.config['bootstrap-host'])
191
151
        self.assertEqual('url', env.config['tools-metadata-url'])
192
152
        self.assertEqual('devel', env.config['agent-stream'])
193
 
        self.assertNotIn('region', env.config)
194
 
 
195
 
    def test_update_env_region(self):
196
 
        env = SimpleEnvironment('foo', {'type': 'paas'})
197
 
        update_env(env, 'bar', region='region-foo')
198
 
        self.assertEqual('region-foo', env.config['region'])
199
 
 
200
 
    def test_update_env_region_none(self):
201
 
        env = SimpleEnvironment('foo',
202
 
                                {'type': 'paas', 'region': 'region-foo'})
203
 
        update_env(env, 'bar', region=None)
204
 
        self.assertEqual('region-foo', env.config['region'])
205
153
 
206
154
    def test_dump_juju_timings(self):
207
 
        env = JujuData('foo', {'type': 'bar'})
 
155
        env = SimpleEnvironment('foo', {'type': 'bar'})
208
156
        client = EnvJujuClient(env, None, None)
209
 
        client._backend.juju_timings = {("juju", "op1"): [1],
210
 
                                        ("juju", "op2"): [2]}
 
157
        client.juju_timings = {("juju", "op1"): [1], ("juju", "op2"): [2]}
211
158
        expected = {"juju op1": [1], "juju op2": [2]}
212
159
        with temp_dir() as fake_dir:
213
160
            dump_juju_timings(client, fake_dir)
216
163
                file_data = json.load(out_file)
217
164
        self.assertEqual(file_data, expected)
218
165
 
219
 
    def test_check_token(self):
220
 
        env = JujuData('foo', {'type': 'local'})
221
 
        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
 
        remote = SSHRemote(client, 'unit', None, series='xenial')
233
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
234
 
                   return_value=remote):
235
 
            with patch.object(remote, 'run', autospec=True,
236
 
                              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)
240
 
        rr_mock.assert_called_once_with(GET_TOKEN_SCRIPT)
241
 
        self.assertTrue(remote.use_juju_ssh)
242
 
        self.assertEqual(
243
 
            ['INFO Waiting for applications to reach ready.',
244
 
             'INFO Retrieving token.',
245
 
             "INFO Token matches expected 'token'"],
246
 
            self.log_stream.getvalue().splitlines())
247
 
 
248
 
    def test_check_token_not_found(self):
249
 
        env = JujuData('foo', {'type': 'local'})
250
 
        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
 
        remote = SSHRemote(client, 'unit', None, series='xenial')
262
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
263
 
                   return_value=remote):
264
 
            with patch.object(remote, 'run', autospec=True,
265
 
                              return_value='') as rr_mock:
266
 
                with patch.object(remote, 'get_address',
267
 
                                  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)
273
 
        self.assertEqual(2, rr_mock.call_count)
274
 
        rr_mock.assert_called_with(GET_TOKEN_SCRIPT)
275
 
        ga_mock.assert_called_once_with()
276
 
        self.assertFalse(remote.use_juju_ssh)
277
 
        self.assertEqual(
278
 
            ['INFO Waiting for applications to reach ready.',
279
 
             'INFO Retrieving token.'],
280
 
            self.log_stream.getvalue().splitlines())
281
 
 
282
 
    def test_check_token_not_found_juju_ssh_broken(self):
283
 
        env = JujuData('foo', {'type': 'local'})
284
 
        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
 
        remote = SSHRemote(client, 'unit', None, series='xenial')
296
 
        with patch('deploy_stack.remote_from_unit', autospec=True,
297
 
                   return_value=remote):
298
 
            with patch.object(remote, 'run', autospec=True,
299
 
                              side_effect=['', 'token']) as rr_mock:
300
 
                with patch.object(remote, 'get_address',
301
 
                                  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)
307
 
        self.assertEqual(2, rr_mock.call_count)
308
 
        rr_mock.assert_called_with(GET_TOKEN_SCRIPT)
309
 
        ga_mock.assert_called_once_with()
310
 
        self.assertFalse(remote.use_juju_ssh)
311
 
        self.assertEqual(
312
 
            ['INFO Waiting for applications to reach ready.',
313
 
             'INFO Retrieving token.',
314
 
             "INFO Token matches expected 'token'",
315
 
             'ERROR juju ssh to unit is broken.'],
316
 
            self.log_stream.getvalue().splitlines())
317
 
 
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
 
    log_level = logging.DEBUG
403
 
 
404
 
 
405
 
class DumpEnvLogsTestCase(FakeHomeTestCase):
406
 
 
407
 
    log_level = logging.DEBUG
 
166
 
 
167
class DumpEnvLogsTestCase(TestCase):
 
168
 
 
169
    def setUp(self):
 
170
        setup_test_logging(self, level=logging.DEBUG)
408
171
 
409
172
    def assert_machines(self, expected, got):
410
173
        self.assertEqual(expected, dict((k, got[k].address) for k in got))
426
189
                               autospec=True) as crl_mock:
427
190
                        with patch('deploy_stack.archive_logs',
428
191
                                   autospec=True) as al_mock:
429
 
                            env = JujuData('foo', {'type': 'nonlocal'})
 
192
                            env = SimpleEnvironment('foo',
 
193
                                                    {'type': 'nonlocal'})
430
194
                            client = EnvJujuClient(env, '1.234-76', None)
431
195
                            dump_env_logs(client, '10.10.0.1', artifacts_dir)
432
196
            al_mock.assert_called_once_with(artifacts_dir)
434
198
                ['machine-0', 'machine-1', 'machine-2'],
435
199
                sorted(os.listdir(artifacts_dir)))
436
200
        self.assertEqual(
437
 
            (client, {'0': '10.10.0.1'}), gm_mock.call_args[0])
 
201
            (client, '10.10.0.1'), gm_mock.call_args[0])
438
202
        self.assertItemsEqual(
439
203
            [(self.r0, '%s/machine-0' % artifacts_dir),
440
204
             (self.r1, '%s/machine-1' % artifacts_dir),
455
219
                               autospec=True) as crl_mock:
456
220
                        with patch('deploy_stack.archive_logs',
457
221
                                   autospec=True) as al_mock:
458
 
                            env = JujuData('foo', {'type': 'nonlocal'})
 
222
                            env = SimpleEnvironment('foo',
 
223
                                                    {'type': 'nonlocal'})
459
224
                            client = EnvJujuClient(env, '1.234-76', None)
460
225
                            dump_env_logs(client, '10.10.0.1', artifacts_dir)
461
226
            al_mock.assert_called_once_with(artifacts_dir)
462
227
            self.assertEqual(
463
228
                ['machine-2'],
464
229
                sorted(os.listdir(artifacts_dir)))
465
 
        self.assertEqual((client, {'0': '10.10.0.1'}), gm_mock.call_args[0])
 
230
        self.assertEqual(
 
231
            (client, '10.10.0.1'), gm_mock.call_args[0])
466
232
        self.assertEqual(
467
233
            [(self.r2, '%s/machine-2' % artifacts_dir)],
468
234
            [cal[0] for cal in crl_mock.call_args_list])
473
239
            self.log_stream.getvalue().splitlines())
474
240
 
475
241
    def test_dump_env_logs_local_env(self):
476
 
        env = JujuData('foo', {'type': 'local'})
 
242
        env = SimpleEnvironment('foo', {'type': 'local'})
477
243
        client = EnvJujuClient(env, '1.234-76', None)
478
244
        with temp_dir() as artifacts_dir:
479
245
            with patch('deploy_stack.get_remote_machines',
499
265
            log_path = os.path.join(log_dir, 'fake.log')
500
266
            cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path])
501
267
 
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
 
    def test_archive_logs_subdir(self):
512
 
        with temp_dir() as log_dir:
513
 
            subdir = os.path.join(log_dir, "subdir")
514
 
            os.mkdir(subdir)
515
 
            with open(os.path.join(subdir, 'fake.log'), 'w') as f:
516
 
                f.write('log contents')
517
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
518
 
                archive_logs(log_dir)
519
 
            log_path = os.path.join(subdir, 'fake.log')
520
 
            cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path])
521
 
 
522
268
    def test_archive_logs_none(self):
523
269
        with temp_dir() as log_dir:
524
270
            with patch('subprocess.check_call', autospec=True) as cc_mock:
525
271
                archive_logs(log_dir)
526
272
        self.assertEquals(cc_mock.call_count, 0)
527
273
 
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
274
    def test_copy_local_logs(self):
549
275
        # Relevent local log files are copied, after changing their permissions
550
276
        # to allow access by non-root user.
596
322
            with patch('subprocess.check_output') as cc_mock:
597
323
                copy_remote_logs(remote_from_address('10.10.0.1'), '/foo')
598
324
        self.assertEqual(
599
 
            (get_timeout_prefix(120) + (
600
 
                'ssh',
601
 
                '-o', 'User ubuntu',
602
 
                '-o', 'UserKnownHostsFile /dev/null',
603
 
                '-o', 'StrictHostKeyChecking no',
604
 
                '-o', 'PasswordAuthentication no',
605
 
                '10.10.0.1',
606
 
                'sudo chmod -Rf go+r /var/log/cloud-init*.log'
607
 
                ' /var/log/juju/*.log'
608
 
                ' /var/lib/juju/containers/juju-*-lxc-*/'
609
 
                ' /var/log/lxd/juju-*'
610
 
                ' /var/log/lxd/lxd.log'
611
 
                ' /var/log/syslog'
612
 
                ' /var/log/mongodb/mongodb.log'
613
 
                ' /etc/network/interfaces'
614
 
                ' /home/ubuntu/ifconfig.log'
615
 
                ),),
 
325
            (['timeout', '5m', 'ssh',
 
326
              '-o', 'User ubuntu',
 
327
              '-o', 'UserKnownHostsFile /dev/null',
 
328
              '-o', 'StrictHostKeyChecking no',
 
329
              '10.10.0.1',
 
330
              'sudo chmod go+r /var/log/juju/*'], ),
616
331
            cc_mock.call_args_list[0][0])
617
332
        self.assertEqual(
618
 
            (get_timeout_prefix(120) + (
619
 
                'ssh',
620
 
                '-o', 'User ubuntu',
621
 
                '-o', 'UserKnownHostsFile /dev/null',
622
 
                '-o', 'StrictHostKeyChecking no',
623
 
                '-o', 'PasswordAuthentication no',
624
 
                '10.10.0.1',
625
 
                'ifconfig > /home/ubuntu/ifconfig.log'),),
 
333
            (['timeout', '5m', 'scp', '-C',
 
334
              '-o', 'User ubuntu',
 
335
              '-o', 'UserKnownHostsFile /dev/null',
 
336
              '-o', 'StrictHostKeyChecking no',
 
337
              '10.10.0.1:/var/log/cloud-init*.log',
 
338
              '10.10.0.1:/var/log/juju/*.log',
 
339
              '/foo'],),
626
340
            cc_mock.call_args_list[1][0])
627
 
        self.assertEqual(
628
 
            (get_timeout_prefix(120) + (
629
 
                'scp', '-rC',
630
 
                '-o', 'User ubuntu',
631
 
                '-o', 'UserKnownHostsFile /dev/null',
632
 
                '-o', 'StrictHostKeyChecking no',
633
 
                '-o', 'PasswordAuthentication no',
634
 
                '10.10.0.1:/var/log/cloud-init*.log',
635
 
                '10.10.0.1:/var/log/juju/*.log',
636
 
                '10.10.0.1:/var/lib/juju/containers/juju-*-lxc-*/',
637
 
                '10.10.0.1:/var/log/lxd/juju-*',
638
 
                '10.10.0.1:/var/log/lxd/lxd.log',
639
 
                '10.10.0.1:/var/log/syslog',
640
 
                '10.10.0.1:/var/log/mongodb/mongodb.log',
641
 
                '10.10.0.1:/etc/network/interfaces',
642
 
                '10.10.0.1:/home/ubuntu/ifconfig.log',
643
 
                '/foo'),),
644
 
            cc_mock.call_args_list[2][0])
645
341
 
646
342
    def test_copy_remote_logs_windows(self):
647
343
        remote = remote_from_address('10.10.0.1', series="win2012hvr2")
665
361
        with patch('subprocess.check_output', side_effect=remote_op) as co:
666
362
            with patch('deploy_stack.wait_for_port', autospec=True):
667
363
                copy_remote_logs(remote_from_address('10.10.0.1'), '/foo')
668
 
        self.assertEqual(3, co.call_count)
 
364
        self.assertEqual(2, co.call_count)
669
365
        self.assertEqual(
670
 
            ["DEBUG ssh -o 'User ubuntu' -o 'UserKnownHostsFile /dev/null' "
671
 
             "-o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' "
672
 
             "10.10.0.1 'sudo chmod -Rf go+r /var/log/cloud-init*.log "
673
 
             "/var/log/juju/*.log /var/lib/juju/containers/juju-*-lxc-*/ "
674
 
             "/var/log/lxd/juju-* "
675
 
             "/var/log/lxd/lxd.log "
676
 
             "/var/log/syslog "
677
 
             "/var/log/mongodb/mongodb.log "
678
 
             "/etc/network/interfaces "
679
 
             "/home/ubuntu/ifconfig.log'",
680
 
             'WARNING Could not allow access to the juju logs:',
 
366
            ['WARNING Could not allow access to the juju logs:',
681
367
             'WARNING None',
682
 
             "DEBUG ssh -o 'User ubuntu' -o 'UserKnownHostsFile /dev/null' "
683
 
             "-o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' "
684
 
             "10.10.0.1 'ifconfig > /home/ubuntu/ifconfig.log'",
685
 
             'WARNING Could not capture ifconfig state:',
686
 
             'WARNING None', 'WARNING Could not retrieve some or all logs:',
687
 
             'WARNING CalledProcessError()',
688
 
             ],
 
368
             'WARNING Could not retrieve some or all logs:',
 
369
             'WARNING None'],
689
370
            self.log_stream.getvalue().splitlines())
690
371
 
691
372
    def test_get_machines_for_logs(self):
692
373
        client = EnvJujuClient(
693
 
            JujuData('cloud', {'type': 'ec2'}), '1.23.4', None)
 
374
            SimpleEnvironment('cloud', {'type': 'ec2'}), '1.23.4', None)
694
375
        status = Status.from_text("""\
695
376
            machines:
696
377
              "0":
700
381
            """)
701
382
        with patch.object(client, 'get_status', autospec=True,
702
383
                          return_value=status):
703
 
            machines = get_remote_machines(client, {})
 
384
            machines = get_remote_machines(client, None)
704
385
        self.assert_machines(
705
386
            {'0': '10.11.12.13', '1': '10.11.12.14'}, machines)
706
387
 
707
388
    def test_get_machines_for_logs_with_boostrap_host(self):
708
389
        client = EnvJujuClient(
709
 
            JujuData('cloud', {'type': 'ec2'}), '1.23.4', None)
 
390
            SimpleEnvironment('cloud', {'type': 'ec2'}), '1.23.4', None)
710
391
        status = Status.from_text("""\
711
392
            machines:
712
393
              "0":
714
395
            """)
715
396
        with patch.object(client, 'get_status', autospec=True,
716
397
                          return_value=status):
717
 
            machines = get_remote_machines(client, {'0': '10.11.111.222'})
 
398
            machines = get_remote_machines(client, '10.11.111.222')
718
399
        self.assert_machines({'0': '10.11.111.222'}, machines)
719
400
 
720
401
    def test_get_machines_for_logs_with_no_addresses(self):
721
402
        client = EnvJujuClient(
722
 
            JujuData('cloud', {'type': 'ec2'}), '1.23.4', None)
 
403
            SimpleEnvironment('cloud', {'type': 'ec2'}), '1.23.4', None)
723
404
        with patch.object(client, 'get_status', autospec=True,
724
405
                          side_effect=Exception):
725
 
            machines = get_remote_machines(client, {'0': '10.11.111.222'})
 
406
            machines = get_remote_machines(client, '10.11.111.222')
726
407
        self.assert_machines({'0': '10.11.111.222'}, machines)
727
408
 
728
409
    @patch('subprocess.check_call')
732
413
            'name': 'foo',
733
414
            'maas-server': 'http://bar/MASS/',
734
415
            'maas-oauth': 'baz'}
735
 
        client = EnvJujuClient(JujuData('cloud', config), '1.23.4', None)
 
416
        client = EnvJujuClient(
 
417
            SimpleEnvironment('cloud', config), '1.23.4', None)
736
418
        status = Status.from_text("""\
737
419
            machines:
738
420
              "0":
748
430
            }
749
431
            with patch('substrate.MAASAccount.get_allocated_ips',
750
432
                       autospec=True, return_value=allocated_ips):
751
 
                machines = get_remote_machines(client, {'0': 'node1.maas'})
 
433
                machines = get_remote_machines(client, 'node1.maas')
752
434
        self.assert_machines(
753
435
            {'0': '10.11.12.13', '1': '10.11.12.14'}, machines)
754
436
 
755
437
    def test_iter_remote_machines(self):
756
438
        client = EnvJujuClient(
757
 
            JujuData('cloud', {'type': 'ec2'}), '1.23.4', None)
 
439
            SimpleEnvironment('cloud', {'type': 'ec2'}), '1.23.4', None)
758
440
        status = Status.from_text("""\
759
441
            machines:
760
442
              "0":
771
453
 
772
454
    def test_iter_remote_machines_with_series(self):
773
455
        client = EnvJujuClient(
774
 
            JujuData('cloud', {'type': 'ec2'}), '1.23.4', None)
 
456
            SimpleEnvironment('cloud', {'type': 'ec2'}), '1.23.4', None)
775
457
        status = Status.from_text("""\
776
458
            machines:
777
459
              "0":
806
488
        rj_mock.assert_called_with('src', 'dst')
807
489
 
808
490
 
809
 
class TestDeployDummyStack(FakeHomeTestCase):
810
 
 
811
 
    def test_deploy_dummy_stack_sets_centos_constraints(self):
812
 
        env = JujuData('foo', {'type': 'maas'})
813
 
        client = EnvJujuClient(env, '2.0.0', '/foo/juju')
814
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
815
 
            with patch.object(EnvJujuClient, 'wait_for_started'):
816
 
                with patch('deploy_stack.get_random_string',
817
 
                           return_value='fake-token', autospec=True):
818
 
                    deploy_dummy_stack(client, 'centos')
819
 
        assert_juju_call(self, cc_mock, client,
820
 
                         ('juju', '--show-log', 'set-model-constraints', '-m',
821
 
                          'foo:foo', 'tags=MAAS_NIC_1'), 0)
822
 
 
823
 
    def test_assess_juju_relations(self):
824
 
        env = JujuData('foo', {'type': 'nonlocal'})
 
491
class TestDeployDummyStack(TestCase):
 
492
 
 
493
    def setUp(self):
 
494
        setup_test_logging(self)
 
495
 
 
496
    def test_deploy_dummy_stack(self):
 
497
        env = SimpleEnvironment('foo', {'type': 'nonlocal'})
825
498
        client = EnvJujuClient(env, None, '/foo/juju')
826
 
        with patch.object(client, 'get_juju_output', side_effect='fake-token',
827
 
                          autospec=True):
828
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
829
 
                with patch('deploy_stack.get_random_string',
830
 
                           return_value='fake-token', autospec=True):
831
 
                    with patch('deploy_stack.check_token',
832
 
                               autospec=True) as ct_mock:
833
 
                        assess_juju_relations(client)
834
 
        assert_juju_call(self, cc_mock, client, (
835
 
            'juju', '--show-log', 'config', '-m', 'foo:foo',
836
 
            'dummy-source', 'token=fake-token'), 0)
837
 
        ct_mock.assert_called_once_with(client, 'fake-token')
838
 
 
839
 
    def test_deploy_dummy_stack_centos(self):
840
 
        client = fake_juju_client()
841
 
        client.bootstrap()
842
 
        with patch.object(client, 'deploy', autospec=True) as dp_mock:
843
 
            with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'):
844
 
                deploy_dummy_stack(client, 'centos7')
845
 
        calls = [
846
 
            call('/tmp/repo/charms-centos/dummy-source', series='centos7'),
847
 
            call('/tmp/repo/charms-centos/dummy-sink', series='centos7')]
848
 
        self.assertEqual(dp_mock.mock_calls, calls)
849
 
 
850
 
    def test_deploy_dummy_stack_win(self):
851
 
        client = fake_juju_client()
852
 
        client.bootstrap()
853
 
        with patch.object(client, 'deploy', autospec=True) as dp_mock:
854
 
            with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'):
855
 
                deploy_dummy_stack(client, 'win2012hvr2')
856
 
        calls = [
857
 
            call('/tmp/repo/charms-win/dummy-source', series='win2012hvr2'),
858
 
            call('/tmp/repo/charms-win/dummy-sink', series='win2012hvr2')]
859
 
        self.assertEqual(dp_mock.mock_calls, calls)
860
 
 
861
 
    def test_deploy_dummy_stack(self):
862
 
        env = JujuData('foo', {'type': 'nonlocal'})
863
 
        client = EnvJujuClient(env, '2.0.0', '/foo/juju')
864
499
        status = yaml.safe_dump({
865
500
            'machines': {'0': {'agent-state': 'started'}},
866
501
            'services': {
870
505
            }
871
506
        })
872
507
 
873
 
        def output(*args, **kwargs):
 
508
        def output(args, **kwargs):
874
509
            output = {
875
 
                ('show-status', '--format', 'yaml'): status,
876
 
                ('ssh', 'dummy-sink/0', GET_TOKEN_SCRIPT): 'fake-token',
 
510
                ('juju', '--show-log', 'status', '-e', 'foo'): status,
 
511
                ('juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0',
 
512
                 GET_TOKEN_SCRIPT): 'fake-token',
877
513
            }
878
514
            return output[args]
879
515
 
880
 
        with patch.object(client, 'get_juju_output', side_effect=output,
881
 
                          autospec=True) as gjo_mock:
882
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
883
 
                with patch('deploy_stack.get_random_string',
884
 
                           return_value='fake-token', autospec=True):
885
 
                    with patch('sys.stdout', autospec=True):
886
 
                        with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'):
887
 
                            deploy_dummy_stack(client, 'bar-')
888
 
        assert_juju_call(self, cc_mock, client, (
889
 
            'juju', '--show-log', 'deploy', '-m', 'foo:foo',
890
 
            '/tmp/repo/charms/dummy-source', '--series', 'bar-'), 0)
891
 
        assert_juju_call(self, cc_mock, client, (
892
 
            'juju', '--show-log', 'deploy', '-m', 'foo:foo',
893
 
            '/tmp/repo/charms/dummy-sink', '--series', 'bar-'), 1)
894
 
        assert_juju_call(self, cc_mock, client, (
895
 
            'juju', '--show-log', 'add-relation', '-m', 'foo:foo',
896
 
            'dummy-source', 'dummy-sink'), 2)
897
 
        assert_juju_call(self, cc_mock, client, (
898
 
            'juju', '--show-log', 'expose', '-m', 'foo:foo', 'dummy-sink'), 3)
899
 
        self.assertEqual(cc_mock.call_count, 4)
900
 
        self.assertEqual(
901
 
            [
902
 
                call('show-status', '--format', 'yaml', controller=False)
903
 
            ],
904
 
            gjo_mock.call_args_list)
905
 
 
906
 
        client = client.clone(version='1.25.0')
907
 
        with patch.object(client, 'get_juju_output', side_effect=output,
908
 
                          autospec=True) as gjo_mock:
909
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
910
 
                with patch('deploy_stack.get_random_string',
911
 
                           return_value='fake-token', autospec=True):
912
 
                    with patch('sys.stdout', autospec=True):
913
 
                        with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'):
914
 
                            deploy_dummy_stack(client, 'bar-')
915
 
        assert_juju_call(self, cc_mock, client, (
916
 
            'juju', '--show-log', 'deploy', '-m', 'foo:foo',
917
 
            'local:bar-/dummy-source', '--series', 'bar-'), 0)
918
 
        assert_juju_call(self, cc_mock, client, (
919
 
            'juju', '--show-log', 'deploy', '-m', 'foo:foo',
920
 
            'local:bar-/dummy-sink', '--series', 'bar-'), 1)
 
516
        with patch('subprocess.check_output', side_effect=output,
 
517
                   autospec=True) as co_mock:
 
518
            with patch('subprocess.check_call', autospec=True) as cc_mock:
 
519
                with patch('deploy_stack.get_random_string',
 
520
                           return_value='fake-token', autospec=True):
 
521
                    with patch('sys.stdout', autospec=True):
 
522
                        deploy_dummy_stack(client, 'bar-')
 
523
        assert_juju_call(self, cc_mock, client, (
 
524
            'juju', '--show-log', 'deploy', '-e', 'foo', 'bar-dummy-source'),
 
525
            0)
 
526
        assert_juju_call(self, cc_mock, client, (
 
527
            'juju', '--show-log', 'set', '-e', 'foo', 'dummy-source',
 
528
            'token=fake-token'), 1)
 
529
        assert_juju_call(self, cc_mock, client, (
 
530
            'juju', '--show-log', 'deploy', '-e', 'foo', 'bar-dummy-sink'), 2)
 
531
        assert_juju_call(self, cc_mock, client, (
 
532
            'juju', '--show-log', 'add-relation', '-e', 'foo',
 
533
            'dummy-source', 'dummy-sink'), 3)
 
534
        assert_juju_call(self, cc_mock, client, (
 
535
            'juju', '--show-log', 'expose', '-e', 'foo', 'dummy-sink'), 4)
 
536
        self.assertEqual(cc_mock.call_count, 5)
 
537
        assert_juju_call(self, co_mock, client, (
 
538
            'juju', '--show-log', 'status', '-e', 'foo'), 0,
 
539
            assign_stderr=True)
 
540
        assert_juju_call(self, co_mock, client, (
 
541
            'juju', '--show-log', 'status', '-e', 'foo'), 1,
 
542
            assign_stderr=True)
 
543
        assert_juju_call(self, co_mock, client, (
 
544
            'juju', '--show-log', 'ssh', '-e', 'foo', 'dummy-sink/0',
 
545
            GET_TOKEN_SCRIPT), 2, assign_stderr=True)
 
546
        self.assertEqual(co_mock.call_count, 3)
921
547
 
922
548
 
923
549
def fake_SimpleEnvironment(name):
928
554
    return EnvJujuClient(env=env, version='1.2.3.4', full_path=path)
929
555
 
930
556
 
931
 
class FakeBootstrapManager:
932
 
 
933
 
    def __init__(self, client, keep_env=False):
934
 
        self.client = client
935
 
        self.tear_down_client = client
936
 
        self.entered_top = False
937
 
        self.exited_top = False
938
 
        self.entered_bootstrap = False
939
 
        self.exited_bootstrap = False
940
 
        self.entered_runtime = False
941
 
        self.exited_runtime = False
942
 
        self.torn_down = False
943
 
        self.permanent = False
944
 
        self.known_hosts = {'0': '0.example.org'}
945
 
        self.keep_env = keep_env
946
 
 
947
 
    @contextmanager
948
 
    def top_context(self):
949
 
        try:
950
 
            self.entered_top = True
951
 
            yield ['bar']
952
 
        finally:
953
 
            self.exited_top = True
954
 
 
955
 
    @contextmanager
956
 
    def bootstrap_context(self, machines):
957
 
        initial_home = self.client.env.juju_home
958
 
        self.client.env.environment = self.client.env.environment + '-temp'
959
 
        self.client.env.controller.name = self.client.env.environment
960
 
        try:
961
 
            self.entered_bootstrap = True
962
 
            self.client.env.juju_home = os.path.join(initial_home, 'isolated')
963
 
            self.client.bootstrap()
964
 
            yield
965
 
        finally:
966
 
            self.exited_bootstrap = True
967
 
            if not self.permanent:
968
 
                self.client.env.juju_home = initial_home
969
 
 
970
 
    @contextmanager
971
 
    def runtime_context(self, machines):
972
 
        try:
973
 
            self.entered_runtime = True
974
 
            yield
975
 
        finally:
976
 
            if not self.keep_env:
977
 
                self.tear_down()
978
 
            self.exited_runtime = True
979
 
 
980
 
    def tear_down(self):
981
 
        tear_down_meth = getattr(
982
 
            self.tear_down_client, 'destroy_environment',
983
 
            self.tear_down_client.kill_controller)
984
 
        tear_down_meth()
985
 
        self.torn_down = True
986
 
 
987
 
    @contextmanager
988
 
    def booted_context(self, upload_tools):
989
 
        with self.top_context() as machines:
990
 
            with self.bootstrap_context(machines):
991
 
                self.client.bootstrap(upload_tools)
992
 
            with self.runtime_context(machines):
993
 
                yield machines
994
 
 
995
 
 
996
 
class TestDeployJob(FakeHomeTestCase):
997
 
 
998
 
    @contextmanager
999
 
    def ds_cxt(self):
1000
 
        env = JujuData('foo', {})
1001
 
        client = fake_EnvJujuClient(env)
1002
 
        bc_cxt = patch('deploy_stack.client_from_config',
1003
 
                       return_value=client)
1004
 
        fc_cxt = patch('jujupy.SimpleEnvironment.from_config',
1005
 
                       return_value=env)
1006
 
        mgr = MagicMock()
1007
 
        bm_cxt = patch('deploy_stack.BootstrapManager', autospec=True,
1008
 
                       return_value=mgr)
1009
 
        juju_cxt = patch('jujupy.EnvJujuClient.juju', autospec=True)
1010
 
        ajr_cxt = patch('deploy_stack.assess_juju_run', autospec=True)
1011
 
        dds_cxt = patch('deploy_stack.deploy_dummy_stack', autospec=True)
1012
 
        with bc_cxt, fc_cxt, bm_cxt as bm_mock, juju_cxt, ajr_cxt, dds_cxt:
1013
 
            yield client, bm_mock
 
557
class TestDeployJob(TestCase):
1014
558
 
1015
559
    @skipIf(sys.platform in ('win32', 'darwin'),
1016
560
            'Not supported on Windown and OS X')
1017
 
    def test_background_chaos_used(self):
1018
 
        args = Namespace(
1019
 
            env='base', juju_bin='/fake/juju', logs='log', temp_env_name='foo',
1020
 
            charm_prefix=None, bootstrap_host=None, machine=None,
1021
 
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1022
 
            keep_env=False, upload_tools=False, with_chaos=1, jes=False,
1023
 
            region=None, verbose=False, upgrade=False, deadline=None,
1024
 
        )
1025
 
        with self.ds_cxt():
1026
 
            with patch('deploy_stack.background_chaos',
1027
 
                       autospec=True) as bc_mock:
1028
 
                with patch('deploy_stack.assess_juju_relations',
1029
 
                           autospec=True):
1030
 
                    with patch('subprocess.Popen', autospec=True,
1031
 
                               return_value=FakePopen('', '', 0)):
1032
 
                        _deploy_job(args, 'local:trusty/', 'trusty')
1033
 
        self.assertEqual(bc_mock.call_count, 1)
 
561
    @patch('jujupy.EnvJujuClient.by_version', side_effect=fake_EnvJujuClient)
 
562
    @patch('jujupy.SimpleEnvironment.from_config',
 
563
           side_effect=fake_SimpleEnvironment)
 
564
    @patch('deploy_stack.boot_context', autospec=True)
 
565
    @patch('deploy_stack.EnvJujuClient.juju', autospec=True)
 
566
    def test_background_chaos_used(self, *args):
 
567
        with patch('deploy_stack.background_chaos', autospec=True) as bc_mock:
 
568
            with patch('deploy_stack.deploy_dummy_stack', autospec=True):
 
569
                with patch('deploy_stack.assess_juju_run', autospec=True):
 
570
                    _deploy_job('foo', None, None, '', None, None, None, 'log',
 
571
                                None, None, None, None, None, None, 1, False,
 
572
                                False,)
1034
573
        self.assertEqual(bc_mock.mock_calls[0][1][0], 'foo')
1035
574
        self.assertEqual(bc_mock.mock_calls[0][1][2], 'log')
1036
575
        self.assertEqual(bc_mock.mock_calls[0][1][3], 1)
1037
576
 
1038
577
    @skipIf(sys.platform in ('win32', 'darwin'),
1039
578
            'Not supported on Windown and OS X')
1040
 
    def test_background_chaos_not_used(self):
1041
 
        args = Namespace(
1042
 
            env='base', juju_bin='/fake/juju', logs='log', temp_env_name='foo',
1043
 
            charm_prefix=None, bootstrap_host=None, machine=None,
1044
 
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1045
 
            keep_env=False, upload_tools=False, with_chaos=0, jes=False,
1046
 
            region=None, verbose=False, upgrade=False, deadline=None,
1047
 
        )
1048
 
        with self.ds_cxt():
1049
 
            with patch('deploy_stack.background_chaos',
1050
 
                       autospec=True) as bc_mock:
1051
 
                with patch('deploy_stack.assess_juju_relations',
1052
 
                           autospec=True):
1053
 
                    with patch('subprocess.Popen', autospec=True,
1054
 
                               return_value=FakePopen('', '', 0)):
1055
 
                        _deploy_job(args, 'local:trusty/', 'trusty')
 
579
    @patch('jujupy.EnvJujuClient.by_version', side_effect=fake_EnvJujuClient)
 
580
    @patch('jujupy.SimpleEnvironment.from_config',
 
581
           side_effect=fake_SimpleEnvironment)
 
582
    @patch('deploy_stack.boot_context', autospec=True)
 
583
    @patch('deploy_stack.EnvJujuClient.juju', autospec=True)
 
584
    def test_background_chaos_not_used(self, *args):
 
585
        with patch('deploy_stack.background_chaos', autospec=True) as bc_mock:
 
586
            with patch('deploy_stack.deploy_dummy_stack', autospec=True):
 
587
                with patch('deploy_stack.assess_juju_run', autospec=True):
 
588
                    _deploy_job('foo', None, None, '', None, None, None, None,
 
589
                                None, None, None, None, None, None, 0, False,
 
590
                                False)
1056
591
        self.assertEqual(bc_mock.call_count, 0)
1057
592
 
1058
 
    def test_region(self):
1059
 
        args = Namespace(
1060
 
            env='base', juju_bin='/fake/juju', logs='log', temp_env_name='foo',
1061
 
            charm_prefix=None, bootstrap_host=None, machine=None,
1062
 
            series='trusty', debug=False, agent_url=None, agent_stream=None,
1063
 
            keep_env=False, upload_tools=False, with_chaos=0, jes=False,
1064
 
            region='region-foo', verbose=False, upgrade=False, deadline=None,
1065
 
        )
1066
 
        with self.ds_cxt() as (client, bm_mock):
1067
 
            with patch('deploy_stack.assess_juju_relations',
1068
 
                       autospec=True):
1069
 
                with patch('subprocess.Popen', autospec=True,
1070
 
                           return_value=FakePopen('', '', 0)):
1071
 
                    _deploy_job(args, 'local:trusty/', 'trusty')
1072
 
                    jes = client.is_jes_enabled()
1073
 
        bm_mock.assert_called_once_with(
1074
 
            'foo', client, client, None, None, 'trusty', None, None,
1075
 
            'region-foo', 'log', False, permanent=jes, jes_enabled=jes)
1076
 
 
1077
 
    def test_deploy_job_changes_series_with_win(self):
1078
 
        args = Namespace(
1079
 
            series='windows', temp_env_name=None, env=None, upgrade=None,
1080
 
            charm_prefix=None, bootstrap_host=None, machine=None, logs=None,
1081
 
            debug=None, juju_bin=None, agent_url=None, agent_stream=None,
1082
 
            keep_env=None, upload_tools=None, with_chaos=None, jes=None,
1083
 
            region=None, verbose=None)
1084
 
        with patch('deploy_stack.deploy_job_parse_args', return_value=args,
1085
 
                   autospec=True):
1086
 
            with patch('deploy_stack._deploy_job', autospec=True) as ds_mock:
1087
 
                deploy_job()
1088
 
        ds_mock.assert_called_once_with(args, 'windows', 'trusty')
1089
 
 
1090
 
    def test_deploy_job_changes_series_with_centos(self):
1091
 
        args = Namespace(
1092
 
            series='centos', temp_env_name=None, env=None, upgrade=None,
1093
 
            charm_prefix=None, bootstrap_host=None, machine=None, logs=None,
1094
 
            debug=None, juju_bin=None, agent_url=None, agent_stream=None,
1095
 
            keep_env=None, upload_tools=None, with_chaos=None, jes=None,
1096
 
            region=None, verbose=None)
1097
 
        with patch('deploy_stack.deploy_job_parse_args', return_value=args,
1098
 
                   autospec=True):
1099
 
            with patch('deploy_stack._deploy_job', autospec=True) as ds_mock:
1100
 
                deploy_job()
1101
 
        ds_mock.assert_called_once_with(args, 'centos', 'trusty')
1102
 
 
1103
 
 
1104
 
class TestTestUpgrade(FakeHomeTestCase):
 
593
 
 
594
class TestTestUpgrade(TestCase):
1105
595
 
1106
596
    RUN_UNAME = (
1107
597
        'juju', '--show-log', 'run', '-e', 'foo', '--format', 'json',
1108
598
        '--service', 'dummy-source,dummy-sink', 'uname')
1109
 
    STATUS = (
1110
 
        'juju', '--show-log', 'show-status', '-m', 'foo:foo',
1111
 
        '--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')
 
599
    VERSION = ('/bar/juju', '--version')
 
600
    STATUS = ('juju', '--show-log', 'status', '-e', 'foo')
 
601
    GET_ENV = ('juju', '--show-log', 'get-env', '-e', 'foo',
 
602
               'tools-metadata-url')
 
603
 
 
604
    def setUp(self):
 
605
        setup_test_logging(self)
1122
606
 
1123
607
    @classmethod
1124
608
    def upgrade_output(cls, args, **kwargs):
1125
609
        status = yaml.safe_dump({
1126
610
            'machines': {'0': {
1127
611
                'agent-state': 'started',
1128
 
                'agent-version': '2.0-rc2'}},
 
612
                'agent-version': '1.38'}},
1129
613
            'services': {}})
1130
614
        juju_run_out = json.dumps([
1131
615
            {"MachineId": "1", "Stdout": "Linux\n"},
1132
616
            {"MachineId": "2", "Stdout": "Linux\n"}])
1133
 
        list_models = json.dumps(
1134
 
            {'models': [
1135
 
                {'name': 'controller'},
1136
 
                {'name': 'foo'},
1137
 
            ]})
1138
617
        output = {
1139
618
            cls.STATUS: status,
1140
 
            cls.CONTROLLER_STATUS: status,
1141
619
            cls.RUN_UNAME: juju_run_out,
1142
 
            cls.GET_ENV: 'testing',
1143
 
            cls.GET_CONTROLLER_ENV: 'testing',
1144
 
            cls.LIST_MODELS: list_models,
 
620
            cls.VERSION: '1.38',
 
621
            cls.GET_ENV: 'testing'
1145
622
        }
1146
 
        return FakePopen(output[args], '', 0)
 
623
        return output[args]
1147
624
 
1148
625
    @contextmanager
1149
626
    def upgrade_mocks(self):
1150
 
        with patch('subprocess.Popen', side_effect=self.upgrade_output,
 
627
        with patch('subprocess.check_output', side_effect=self.upgrade_output,
1151
628
                   autospec=True) as co_mock:
1152
629
            with patch('subprocess.check_call', autospec=True) as cc_mock:
1153
630
                with patch('deploy_stack.check_token', autospec=True):
1154
631
                    with patch('deploy_stack.get_random_string',
1155
632
                               return_value="FAKETOKEN", autospec=True):
1156
 
                        with patch('jujupy.EnvJujuClient.get_version',
1157
 
                                   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)
 
633
                        with patch('sys.stdout', autospec=True):
 
634
                            yield (co_mock, cc_mock)
1163
635
 
1164
636
    def test_assess_upgrade(self):
1165
 
        env = JujuData('foo', {'type': 'foo'})
 
637
        env = SimpleEnvironment('foo', {'type': 'foo'})
1166
638
        old_client = EnvJujuClient(env, None, '/foo/juju')
1167
639
        with self.upgrade_mocks() as (co_mock, cc_mock):
1168
640
            assess_upgrade(old_client, '/bar/juju')
1169
641
        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)
 
642
        assert_juju_call(self, cc_mock, new_client, (
 
643
            'juju', '--show-log', 'upgrade-juju', '-e', 'foo', '--version',
 
644
            '1.38'), 0)
 
645
        assert_juju_call(self, cc_mock, new_client, (
 
646
            'juju', '--show-log', 'set', '-e', 'foo', 'dummy-source',
 
647
            'token=FAKETOKEN'), 1)
1177
648
        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')
 
649
        self.assertEqual(co_mock.mock_calls[0], call(self.VERSION))
 
650
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 1,
 
651
                         assign_stderr=True)
 
652
        assert_juju_call(self, co_mock, new_client, self.GET_ENV, 2,
 
653
                         assign_stderr=True)
 
654
        assert_juju_call(self, co_mock, new_client, self.STATUS, 3,
 
655
                         assign_stderr=True)
 
656
        assert_juju_call(self, co_mock, new_client, self.RUN_UNAME, 4,
 
657
                         assign_stderr=True)
 
658
        self.assertEqual(co_mock.call_count, 5)
1211
659
 
1212
660
    def test_mass_timeout(self):
1213
661
        config = {'type': 'foo'}
1214
 
        old_client = EnvJujuClient(JujuData('foo', config), None, '/foo/juju')
 
662
        old_client = EnvJujuClient(SimpleEnvironment('foo', config),
 
663
                                   None, '/foo/juju')
1215
664
        with self.upgrade_mocks():
1216
665
            with patch.object(EnvJujuClient, 'wait_for_version') as wfv_mock:
1217
666
                assess_upgrade(old_client, '/bar/juju')
1218
 
            wfv_mock.assert_has_calls([call('2.0-rc2', 600)] * 2)
 
667
            wfv_mock.assert_called_once_with('1.38', 600)
1219
668
            config['type'] = 'maas'
1220
669
            with patch.object(EnvJujuClient, 'wait_for_version') as wfv_mock:
1221
670
                assess_upgrade(old_client, '/bar/juju')
1222
 
        wfv_mock.assert_has_calls([call('2.0-rc2', 1200)] * 2)
1223
 
 
1224
 
 
1225
 
class TestBootstrapManager(FakeHomeTestCase):
1226
 
 
1227
 
    def test_from_args(self):
1228
 
        deadline = datetime(2012, 11, 10, 9, 8, 7)
1229
 
        args = Namespace(
1230
 
            env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
1231
 
            bootstrap_host='example.org', machine=['example.com'],
1232
 
            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)
1239
 
        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)
1242
 
        self.assertEqual('example.org', bs_manager.bootstrap_host)
1243
 
        self.assertEqual(['example.com'], bs_manager.machines)
1244
 
        self.assertEqual('angsty', bs_manager.series)
1245
 
        self.assertEqual('qux', bs_manager.agent_url)
1246
 
        self.assertEqual('escaped', bs_manager.agent_stream)
1247
 
        self.assertEqual('eu-west-northwest-5', bs_manager.region)
1248
 
        self.assertIs(True, bs_manager.keep_env)
1249
 
        self.assertEqual('pine', bs_manager.log_dir)
1250
 
        jes_enabled = bs_manager.client.is_jes_enabled.return_value
1251
 
        self.assertEqual(jes_enabled, bs_manager.permanent)
1252
 
        self.assertEqual(jes_enabled, bs_manager.jes_enabled)
1253
 
        self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts)
1254
 
 
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
 
    def test_jes_not_permanent(self):
1287
 
        with self.assertRaisesRegexp(ValueError, 'Cannot set permanent False'
1288
 
                                     ' if jes_enabled is True.'):
1289
 
            BootstrapManager(
1290
 
                jes_enabled=True, permanent=False,
1291
 
                temp_env_name=None, client=None, tear_down_client=None,
1292
 
                bootstrap_host=None, machines=[], series=None, agent_url=None,
1293
 
                agent_stream=None, region=None, log_dir=None, keep_env=None)
1294
 
 
1295
 
    def test_aws_machines_updates_bootstrap_host(self):
1296
 
        client = fake_juju_client()
1297
 
        client.env.config['type'] = 'manual'
1298
 
        bs_manager = BootstrapManager(
1299
 
            'foobar', client, client, None, [], None, None, None, None,
1300
 
            client.env.juju_home, False, False, False)
1301
 
        with patch('deploy_stack.run_instances',
1302
 
                   return_value=[('foo', 'aws.example.org')]):
1303
 
            with patch('deploy_stack.destroy_job_instances'):
1304
 
                with bs_manager.aws_machines():
1305
 
                    self.assertEqual({'0': 'aws.example.org'},
1306
 
                                     bs_manager.known_hosts)
1307
 
 
1308
 
    def test_from_args_no_host(self):
1309
 
        args = Namespace(
1310
 
            env='foo', juju_bin='bar', debug=True, temp_env_name='baz',
1311
 
            bootstrap_host=None, machine=['example.com'],
1312
 
            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)
1317
 
        self.assertIs(None, bs_manager.bootstrap_host)
1318
 
        self.assertEqual({}, bs_manager.known_hosts)
1319
 
 
1320
 
    def make_client(self):
1321
 
        client = MagicMock()
1322
 
        client.env = SimpleEnvironment(
1323
 
            'foo', {'type': 'baz'}, use_context(self, temp_dir()))
1324
 
        client.is_jes_enabled.return_value = False
1325
 
        client.get_matching_agent_version.return_value = '3.14'
1326
 
        client.get_cache_path.return_value = get_cache_path(
1327
 
            client.env.juju_home)
1328
 
        return client
1329
 
 
1330
 
    def test_bootstrap_context_tear_down(self):
1331
 
        client = fake_juju_client()
1332
 
        client.env.juju_home = use_context(self, temp_dir())
1333
 
        initial_home = client.env.juju_home
1334
 
        bs_manager = BootstrapManager(
1335
 
            'foobar', client, client, None, [], None, None, None, None,
1336
 
            client.env.juju_home, False, False, False)
1337
 
 
1338
 
        def check_config(client_, jes_enabled, try_jes=False):
1339
 
            self.assertEqual(0, client.is_jes_enabled.call_count)
1340
 
            jenv_path = get_jenv_path(client.env.juju_home, 'foobar')
1341
 
            self.assertFalse(os.path.exists(jenv_path))
1342
 
            environments_path = get_environments_path(client.env.juju_home)
1343
 
            self.assertTrue(os.path.isfile(environments_path))
1344
 
            self.assertNotEqual(initial_home, client.env.juju_home)
1345
 
 
1346
 
        ije_cxt = patch.object(client, 'is_jes_enabled')
1347
 
        with patch('deploy_stack.tear_down',
1348
 
                   side_effect=check_config) as td_mock, ije_cxt:
1349
 
            with bs_manager.bootstrap_context([]):
1350
 
                td_mock.assert_called_once_with(client, False, try_jes=True)
1351
 
 
1352
 
    def test_bootstrap_context_tear_down_jenv(self):
1353
 
        client = self.make_client()
1354
 
        initial_home = client.env.juju_home
1355
 
        jenv_path = get_jenv_path(client.env.juju_home, 'foobar')
1356
 
        os.makedirs(os.path.dirname(jenv_path))
1357
 
        with open(jenv_path, 'w'):
1358
 
            pass
1359
 
 
1360
 
        bs_manager = BootstrapManager(
1361
 
            'foobar', client, client, None, [], None, None, None, None,
1362
 
            client.env.juju_home, False, False, False)
1363
 
 
1364
 
        def check_config(client_, jes_enabled, try_jes=False):
1365
 
            self.assertEqual(0, client.is_jes_enabled.call_count)
1366
 
            self.assertTrue(os.path.isfile(jenv_path))
1367
 
            environments_path = get_environments_path(client.env.juju_home)
1368
 
            self.assertFalse(os.path.exists(environments_path))
1369
 
            self.assertEqual(initial_home, client.env.juju_home)
1370
 
 
1371
 
        with patch('deploy_stack.tear_down',
1372
 
                   side_effect=check_config) as td_mock:
1373
 
            with bs_manager.bootstrap_context([]):
1374
 
                td_mock.assert_called_once_with(client, False, try_jes=False)
1375
 
 
1376
 
    def test_bootstrap_context_tear_down_client(self):
1377
 
        client = self.make_client()
1378
 
        tear_down_client = self.make_client()
1379
 
        tear_down_client.env = client.env
1380
 
        bs_manager = BootstrapManager(
1381
 
            'foobar', client, tear_down_client, None, [], None, None, None,
1382
 
            None, client.env.juju_home, False, False, False)
1383
 
 
1384
 
        def check_config(client_, jes_enabled, try_jes=False):
1385
 
            self.assertEqual(0, client.is_jes_enabled.call_count)
1386
 
            tear_down_client.is_jes_enabled.assert_called_once_with()
1387
 
 
1388
 
        with patch('deploy_stack.tear_down',
1389
 
                   side_effect=check_config) as td_mock:
1390
 
            with bs_manager.bootstrap_context([]):
1391
 
                td_mock.assert_called_once_with(tear_down_client,
1392
 
                                                False, try_jes=True)
1393
 
 
1394
 
    def test_bootstrap_context_tear_down_client_jenv(self):
1395
 
        client = self.make_client()
1396
 
        tear_down_client = self.make_client()
1397
 
        tear_down_client.env = client.env
1398
 
        jenv_path = get_jenv_path(client.env.juju_home, 'foobar')
1399
 
        os.makedirs(os.path.dirname(jenv_path))
1400
 
        with open(jenv_path, 'w'):
1401
 
            pass
1402
 
 
1403
 
        bs_manager = BootstrapManager(
1404
 
            'foobar', client, tear_down_client,
1405
 
            None, [], None, None, None, None, client.env.juju_home, False,
1406
 
            False, False)
1407
 
 
1408
 
        def check_config(client_, jes_enabled, try_jes=False):
1409
 
            self.assertEqual(0, client.is_jes_enabled.call_count)
1410
 
            tear_down_client.is_jes_enabled.assert_called_once_with()
1411
 
 
1412
 
        with patch('deploy_stack.tear_down',
1413
 
                   side_effect=check_config) as td_mock:
1414
 
            with bs_manager.bootstrap_context([]):
1415
 
                td_mock.assert_called_once_with(tear_down_client, False,
1416
 
                                                try_jes=False)
1417
 
 
1418
 
    def test_bootstrap_context_no_set_home(self):
1419
 
        orig_home = get_juju_home()
1420
 
        client = self.make_client()
1421
 
        jenv_path = get_jenv_path(client.env.juju_home, 'foobar')
1422
 
        os.makedirs(os.path.dirname(jenv_path))
1423
 
        with open(jenv_path, 'w'):
1424
 
            pass
1425
 
 
1426
 
        bs_manager = BootstrapManager(
1427
 
            'foobar', client, client, None, [], None, None, None, None,
1428
 
            client.env.juju_home, False, False, False)
1429
 
        with bs_manager.bootstrap_context([]):
1430
 
            self.assertEqual(orig_home, get_juju_home())
1431
 
 
1432
 
    def test_bootstrap_context_calls_update_env(self):
1433
 
        client = fake_juju_client()
1434
 
        client.env.juju_home = use_context(self, temp_dir())
1435
 
        ue_mock = use_context(
1436
 
            self, patch('deploy_stack.update_env', wraps=update_env))
1437
 
        wfp_mock = use_context(
1438
 
            self, patch('deploy_stack.wait_for_port', autospec=True))
1439
 
        bs_manager = BootstrapManager(
1440
 
            'bar', client, client, None,
1441
 
            [], 'wacky', 'url', 'devel', None, client.env.juju_home, False,
1442
 
            True, True)
1443
 
        bs_manager.known_hosts['0'] = 'bootstrap.example.org'
1444
 
        with bs_manager.bootstrap_context([]):
1445
 
            pass
1446
 
        ue_mock.assert_called_with(
1447
 
            client.env, 'bar', series='wacky',
1448
 
            bootstrap_host='bootstrap.example.org',
1449
 
            agent_url='url', agent_stream='devel', region=None)
1450
 
        wfp_mock.assert_called_once_with(
1451
 
            'bootstrap.example.org', 22, timeout=120)
1452
 
 
1453
 
    def test_bootstrap_context_calls_update_env_omit(self):
1454
 
        client = fake_juju_client()
1455
 
        client.env.juju_home = use_context(self, temp_dir())
1456
 
        ue_mock = use_context(
1457
 
            self, patch('deploy_stack.update_env', wraps=update_env))
1458
 
        wfp_mock = use_context(
1459
 
            self, patch('deploy_stack.wait_for_port', autospec=True))
1460
 
        bs_manager = BootstrapManager(
1461
 
            'bar', client, client, None,
1462
 
            [], 'wacky', 'url', 'devel', None, client.env.juju_home, True,
1463
 
            True, True)
1464
 
        bs_manager.known_hosts['0'] = 'bootstrap.example.org'
1465
 
        with bs_manager.bootstrap_context(
1466
 
                [], omit_config={'bootstrap_host', 'series'}):
1467
 
            pass
1468
 
        ue_mock.assert_called_with(client.env, 'bar', agent_url='url',
1469
 
                                   agent_stream='devel', region=None)
1470
 
        wfp_mock.assert_called_once_with(
1471
 
            'bootstrap.example.org', 22, timeout=120)
1472
 
 
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
 
    def test_tear_down_requires_same_env(self):
1502
 
        client = self.make_client()
1503
 
        client.env.juju_home = 'foobar'
1504
 
        tear_down_client = self.make_client()
1505
 
        tear_down_client.env.juju_home = 'barfoo'
1506
 
        bs_manager = BootstrapManager(
1507
 
            'foobar', client, tear_down_client,
1508
 
            None, [], None, None, None, None, client.env.juju_home, False,
1509
 
            False, False)
1510
 
 
1511
 
        def check_home(foo, bar, try_jes):
1512
 
            self.assertEqual(client.env.juju_home,
1513
 
                             tear_down_client.env.juju_home)
1514
 
 
1515
 
        with self.assertRaisesRegexp(AssertionError,
1516
 
                                     'Tear down client needs same env'):
1517
 
            with patch('deploy_stack.tear_down', autospec=True,
1518
 
                       side_effect=check_home):
1519
 
                bs_manager.tear_down()
1520
 
        self.assertEqual('barfoo', tear_down_client.env.juju_home)
1521
 
 
1522
 
    def test_dump_all_no_jes_one_model(self):
1523
 
        client = fake_juju_client()
1524
 
        client.bootstrap()
1525
 
        with temp_dir() as log_dir:
1526
 
            bs_manager = BootstrapManager(
1527
 
                'foobar', client, client,
1528
 
                None, [], None, None, None, None, log_dir, False,
1529
 
                False, jes_enabled=False)
1530
 
            with patch('deploy_stack.dump_env_logs_known_hosts'):
1531
 
                with patch.object(client, 'iter_model_clients') as imc_mock:
1532
 
                    bs_manager.dump_all_logs()
1533
 
        self.assertEqual(0, imc_mock.call_count)
1534
 
 
1535
 
    def test_dump_all_multi_model(self):
1536
 
        client = fake_juju_client()
1537
 
        client.bootstrap()
1538
 
        with temp_dir() as log_dir:
1539
 
            bs_manager = BootstrapManager(
1540
 
                'foobar', client, client,
1541
 
                None, [], None, None, None, None, log_dir, False,
1542
 
                permanent=True, jes_enabled=True)
1543
 
            with patch('deploy_stack.dump_env_logs_known_hosts') as del_mock:
1544
 
                with patch.object(bs_manager, '_should_dump',
1545
 
                                  return_value=True):
1546
 
                    bs_manager.dump_all_logs()
1547
 
 
1548
 
        clients = dict((c[1][0].env.environment, c[1][0])
1549
 
                       for c in del_mock.mock_calls)
1550
 
        self.assertItemsEqual(
1551
 
            [call(client, os.path.join(log_dir, 'name'), None, {}),
1552
 
             call(clients['controller'], os.path.join(log_dir, 'controller'),
1553
 
                  'foo/models/cache.yaml', {})],
1554
 
            del_mock.mock_calls)
1555
 
 
1556
 
    def test_dump_all_multi_model_iter_failure(self):
1557
 
        client = fake_juju_client()
1558
 
        client.bootstrap()
1559
 
        with temp_dir() as log_dir:
1560
 
            bs_manager = BootstrapManager(
1561
 
                'foobar', client, client,
1562
 
                None, [], None, None, None, None, log_dir, False,
1563
 
                permanent=True, jes_enabled=True)
1564
 
            with patch('deploy_stack.dump_env_logs_known_hosts') as del_mock:
1565
 
                with patch.object(client, 'iter_model_clients',
1566
 
                                  side_effect=Exception):
1567
 
                    with patch.object(bs_manager, '_should_dump',
1568
 
                                      return_value=True):
1569
 
                        bs_manager.dump_all_logs()
1570
 
 
1571
 
        clients = dict((c[1][0].env.environment, c[1][0])
1572
 
                       for c in del_mock.mock_calls)
1573
 
 
1574
 
        self.assertItemsEqual(
1575
 
            [call(client, os.path.join(log_dir, 'name'), None, {}),
1576
 
             call(clients['controller'], os.path.join(log_dir, 'controller'),
1577
 
                  'foo/models/cache.yaml', {})],
1578
 
            del_mock.mock_calls)
1579
 
 
1580
 
    def test_dump_all_logs_uses_known_hosts(self):
1581
 
        client = fake_juju_client_optional_jes(jes_enabled=False)
1582
 
        with temp_dir() as log_dir:
1583
 
            bs_manager = BootstrapManager(
1584
 
                'foobar', client, client,
1585
 
                None, [], None, None, None, None, log_dir, False,
1586
 
                False, False)
1587
 
            bs_manager.known_hosts['2'] = 'example.org'
1588
 
            client.bootstrap()
1589
 
            with patch('deploy_stack.dump_env_logs_known_hosts') as del_mock:
1590
 
                with patch.object(bs_manager, '_should_dump',
1591
 
                                  return_value=True):
1592
 
                    bs_manager.dump_all_logs()
1593
 
        del_mock.assert_called_once_with(
1594
 
            client, os.path.join(log_dir, 'name'),
1595
 
            'foo/environments/name.jenv', {
1596
 
                '2': 'example.org',
1597
 
                })
1598
 
 
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
 
    def test_runtime_context_looks_up_host(self):
1618
 
        client = fake_juju_client()
1619
 
        client.bootstrap()
1620
 
        bs_manager = BootstrapManager(
1621
 
            'foobar', client, client,
1622
 
            None, [], None, None, None, None, client.env.juju_home, False,
1623
 
            True, True)
1624
 
        with patch.object(bs_manager, 'dump_all_logs', autospec=True):
1625
 
            with bs_manager.runtime_context([]):
1626
 
                self.assertEqual({
1627
 
                    '0': '0.example.com'}, bs_manager.known_hosts)
1628
 
 
1629
 
    @patch('deploy_stack.dump_env_logs_known_hosts', autospec=True)
1630
 
    def test_runtime_context_addable_machines_no_known_hosts(self, del_mock):
1631
 
        client = fake_juju_client()
1632
 
        client.bootstrap()
1633
 
        bs_manager = BootstrapManager(
1634
 
            'foobar', client, client,
1635
 
            None, [], None, None, None, None, client.env.juju_home, False,
1636
 
            True, True)
1637
 
        bs_manager.known_hosts = {}
1638
 
        with patch.object(bs_manager.client, 'add_ssh_machines',
1639
 
                          autospec=True) as ads_mock:
1640
 
            with patch.object(bs_manager, 'dump_all_logs', autospec=True):
1641
 
                with bs_manager.runtime_context(['baz']):
1642
 
                    ads_mock.assert_called_once_with(['baz'])
1643
 
 
1644
 
    @patch('deploy_stack.BootstrapManager.dump_all_logs', autospec=True)
1645
 
    def test_runtime_context_addable_machines_with_known_hosts(self, dal_mock):
1646
 
        client = fake_juju_client()
1647
 
        client.bootstrap()
1648
 
        with temp_dir() as log_dir:
1649
 
            bs_manager = BootstrapManager(
1650
 
                'foobar', client, client,
1651
 
                None, [], None, None, None, None, log_dir, False,
1652
 
                True, True)
1653
 
            bs_manager.known_hosts['0'] = 'example.org'
1654
 
            with patch.object(bs_manager.client, 'add_ssh_machines',
1655
 
                              autospec=True) as ads_mock:
1656
 
                with bs_manager.runtime_context(['baz']):
1657
 
                    ads_mock.assert_called_once_with(['baz'])
1658
 
 
1659
 
    def test_booted_context_handles_logged_exception(self):
1660
 
        client = fake_juju_client()
1661
 
        with temp_dir() as root:
1662
 
            log_dir = os.path.join(root, 'log-dir')
1663
 
            os.mkdir(log_dir)
1664
 
            bs_manager = BootstrapManager(
1665
 
                'foobar', client, client,
1666
 
                None, [], None, None, None, None, log_dir, False,
1667
 
                True, True)
1668
 
            juju_home = os.path.join(root, 'juju-home')
1669
 
            os.mkdir(juju_home)
1670
 
            client.env.juju_home = juju_home
1671
 
            with self.assertRaises(SystemExit):
1672
 
                with patch.object(bs_manager, 'dump_all_logs'):
1673
 
                    with bs_manager.booted_context(False):
1674
 
                        raise LoggedException()
1675
 
 
1676
 
    def test_booted_context_omits_supported(self):
1677
 
        client = fake_juju_client()
1678
 
        client.env.juju_home = use_context(self, temp_dir())
1679
 
        client.bootstrap_replaces = {'agent-version', 'series',
1680
 
                                     'bootstrap-host', 'agent-stream'}
1681
 
        ue_mock = use_context(
1682
 
            self, patch('deploy_stack.update_env', wraps=update_env))
1683
 
        wfp_mock = use_context(
1684
 
            self, patch('deploy_stack.wait_for_port', autospec=True))
1685
 
        bs_manager = BootstrapManager(
1686
 
            'bar', client, client, 'bootstrap.example.org',
1687
 
            [], 'wacky', 'url', 'devel', None, client.env.juju_home, False,
1688
 
            True, True)
1689
 
        with patch.object(bs_manager, 'runtime_context'):
1690
 
            with bs_manager.booted_context([]):
1691
 
                pass
1692
 
        self.assertEqual({
1693
 
            'name': 'bar',
1694
 
            'default-series': 'wacky',
1695
 
            'agent-metadata-url': 'url',
1696
 
            'type': 'foo',
1697
 
            'region': 'bar',
1698
 
            'test-mode': True,
1699
 
            }, client.get_model_config())
1700
 
        ue_mock.assert_called_with(client.env, 'bar', agent_url='url',
1701
 
                                   region=None)
1702
 
        wfp_mock.assert_called_once_with(
1703
 
            'bootstrap.example.org', 22, timeout=120)
1704
 
 
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
 
 
1804
 
class TestBootContext(FakeHomeTestCase):
 
671
        wfv_mock.assert_called_once_with('1.38', 1200)
 
672
 
 
673
 
 
674
class TestBootContext(TestCase):
1805
675
 
1806
676
    def setUp(self):
1807
 
        super(TestBootContext, self).setUp()
 
677
        self.addContext(patch('subprocess.Popen', side_effect=Exception))
1808
678
        self.addContext(patch('sys.stdout'))
1809
679
 
1810
680
    def addContext(self, cxt):
1812
682
 
1813
683
        :return: The value emitted by cxt.__enter__.
1814
684
        """
1815
 
        return use_context(self, cxt)
 
685
        result = cxt.__enter__()
 
686
        self.addCleanup(lambda: cxt.__exit__(None, None, None))
 
687
        return result
1816
688
 
1817
689
    @contextmanager
1818
 
    def bc_context(self, client, log_dir=None, jes=None, keep_env=False):
1819
 
        dal_mock = self.addContext(
1820
 
            patch('deploy_stack.BootstrapManager.dump_all_logs'))
 
690
    def bc_context(self, client, log_dir=None, jes=False, keep_env=False):
 
691
        dl_mock = self.addContext(patch('deploy_stack.dump_env_logs'))
1821
692
        self.addContext(patch('deploy_stack.get_machine_dns_name',
1822
693
                              return_value='foo', autospec=True))
1823
 
        if isinstance(client, EnvJujuClient1X):
1824
 
            models = []
1825
 
        else:
1826
 
            models = [{'name': 'controller'}, {'name': 'bar'}]
1827
 
        self.addContext(patch.object(client, '_get_models',
1828
 
                                     return_value=models, autospec=True))
1829
 
        c_mock = self.addContext(patch('subprocess.call', autospec=True,
1830
 
                                 return_value=0))
1831
 
        juju_name = os.path.basename(client.full_path)
 
694
        c_mock = self.addContext(patch('subprocess.call', autospec=True))
1832
695
        if jes:
1833
 
            output = jes
1834
 
            po_count = 0
 
696
            co_return = 'system'
1835
697
        else:
1836
 
            output = ''
1837
 
            po_count = 2
1838
 
        with patch('subprocess.Popen', autospec=True,
1839
 
                   return_value=FakePopen(output, '', 0)) as po_mock:
 
698
            co_return = ''
 
699
        with patch('subprocess.check_output', autospec=True,
 
700
                   return_value=co_return) as co_mock:
1840
701
            yield
1841
 
        for help_index in range(po_count):
1842
 
            assert_juju_call(self, po_mock, client, (
1843
 
                juju_name, '--show-log', 'help', 'commands'),
1844
 
                call_index=help_index)
1845
 
        self.assertEqual(po_count, po_mock.call_count)
1846
 
        dal_mock.assert_called_once_with()
 
702
        assert_juju_call(self, co_mock, client, (
 
703
            'juju', '--show-log', 'help', 'commands'), assign_stderr=True)
 
704
        if jes:
 
705
            runtime_config = os.path.join(client.juju_home, 'environments',
 
706
                                          'cache.yaml')
 
707
        else:
 
708
            runtime_config = os.path.join(client.juju_home, 'environments',
 
709
                                          'bar.jenv')
 
710
        dl_mock.assert_called_once_with(
 
711
            client, 'foo', log_dir, runtime_config=runtime_config)
1847
712
        if keep_env:
1848
 
            tear_down_count = 1
 
713
            self.assertEqual(c_mock.call_count, 0)
1849
714
        else:
1850
 
            tear_down_count = 2
1851
 
        for call_index in range(tear_down_count):
1852
 
            if jes:
1853
 
                assert_juju_call(
1854
 
                    self, c_mock, client, get_timeout_prefix(600) + (
1855
 
                        juju_name, '--show-log', jes, 'bar', '-y'), call_index)
1856
 
            else:
1857
 
                assert_juju_call(
1858
 
                    self, c_mock, client, get_timeout_prefix(600) + (
1859
 
                        juju_name, '--show-log', 'destroy-environment', 'bar',
1860
 
                        '-y'), call_index)
1861
 
        self.assertEqual(tear_down_count, c_mock.call_count)
 
715
            assert_juju_call(self, c_mock, client, (
 
716
                'timeout', '600.00s', 'juju', '--show-log',
 
717
                'destroy-environment', 'bar', '--force', '-y'))
1862
718
 
1863
719
    def test_bootstrap_context(self):
1864
720
        cc_mock = self.addContext(patch('subprocess.check_call'))
1865
 
        client = EnvJujuClient(JujuData(
1866
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path')
1867
 
        with self.bc_context(client, 'log_dir', jes='kill-controller'):
1868
 
            with observable_temp_file() as config_file:
1869
 
                with boot_context('bar', client, None, [], None, None, None,
1870
 
                                  'log_dir', keep_env=False,
1871
 
                                  upload_tools=False):
1872
 
                    pass
1873
 
        assert_juju_call(self, cc_mock, client, (
1874
 
            'path', '--show-log', 'bootstrap', '--constraints',
1875
 
            'mem=2G', 'bar', 'paas/qux', '--config', config_file.name,
1876
 
            '--default-model', 'bar', '--agent-version', '1.23'), 0)
1877
 
        assert_juju_call(self, cc_mock, client, (
1878
 
            'path', '--show-log', 'list-controllers'), 1)
1879
 
        assert_juju_call(self, cc_mock, client, (
1880
 
            'path', '--show-log', 'list-models', '-c', 'bar'), 2)
1881
 
        assert_juju_call(self, cc_mock, client, (
1882
 
            'path', '--show-log', 'show-status', '-m', 'bar:controller',
1883
 
            '--format', 'yaml'), 3)
1884
 
        assert_juju_call(self, cc_mock, client, (
1885
 
            'path', '--show-log', 'show-status', '-m', 'bar:bar',
1886
 
            '--format', 'yaml'), 4)
1887
 
 
1888
 
    def test_bootstrap_context_non_jes(self):
1889
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
1890
 
        client = EnvJujuClient1X(SimpleEnvironment(
 
721
        client = EnvJujuClient(SimpleEnvironment(
1891
722
            'foo', {'type': 'paas'}), '1.23', 'path')
1892
723
        with self.bc_context(client, 'log_dir'):
1893
724
            with boot_context('bar', client, None, [], None, None, None,
1894
725
                              'log_dir', keep_env=False, upload_tools=False):
1895
726
                pass
1896
727
        assert_juju_call(self, cc_mock, client, (
1897
 
            'path', '--show-log', 'bootstrap', '-e', 'bar', '--constraints',
 
728
            'juju', '--show-log', 'bootstrap', '-e', 'bar', '--constraints',
1898
729
            'mem=2G'), 0)
1899
730
        assert_juju_call(self, cc_mock, client, (
1900
 
            'path', '--show-log', 'status', '-e', 'bar',
1901
 
            '--format', 'yaml'), 1)
 
731
            'juju', '--show-log', 'status', '-e', 'bar'), 1)
1902
732
 
1903
733
    def test_keep_env(self):
1904
734
        cc_mock = self.addContext(patch('subprocess.check_call'))
1905
 
        client = EnvJujuClient(JujuData(
1906
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path')
1907
 
        with self.bc_context(client, keep_env=True, jes='kill-controller'):
1908
 
            with observable_temp_file() as config_file:
1909
 
                with boot_context('bar', client, None, [], None, None, None,
1910
 
                                  None, keep_env=True, upload_tools=False):
1911
 
                    pass
1912
 
        assert_juju_call(self, cc_mock, client, (
1913
 
            'path', '--show-log', 'bootstrap', '--constraints',
1914
 
            'mem=2G', 'bar', 'paas/qux', '--config', config_file.name,
1915
 
            '--default-model', 'bar', '--agent-version', '1.23'), 0)
1916
 
        assert_juju_call(self, cc_mock, client, (
1917
 
            'path', '--show-log', 'list-controllers'), 1)
1918
 
        assert_juju_call(self, cc_mock, client, (
1919
 
            'path', '--show-log', 'list-models', '-c', 'bar'), 2)
1920
 
        assert_juju_call(self, cc_mock, client, (
1921
 
            'path', '--show-log', 'show-status', '-m', 'bar:controller',
1922
 
            '--format', 'yaml'), 3)
1923
 
        assert_juju_call(self, cc_mock, client, (
1924
 
            'path', '--show-log', 'show-status', '-m', 'bar:bar',
1925
 
            '--format', 'yaml'), 4)
1926
 
 
1927
 
    def test_keep_env_non_jes(self):
1928
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
1929
 
        client = EnvJujuClient1X(SimpleEnvironment(
 
735
        client = EnvJujuClient(SimpleEnvironment(
1930
736
            'foo', {'type': 'paas'}), '1.23', 'path')
1931
737
        with self.bc_context(client, keep_env=True):
1932
738
            with boot_context('bar', client, None, [], None, None, None, None,
1933
739
                              keep_env=True, upload_tools=False):
1934
740
                pass
1935
741
        assert_juju_call(self, cc_mock, client, (
1936
 
            'path', '--show-log', 'bootstrap', '-e', 'bar', '--constraints',
 
742
            'juju', '--show-log', 'bootstrap', '-e', 'bar', '--constraints',
1937
743
            'mem=2G'), 0)
1938
744
        assert_juju_call(self, cc_mock, client, (
1939
 
            'path', '--show-log', 'status', '-e', 'bar',
1940
 
            '--format', 'yaml'), 1)
 
745
            'juju', '--show-log', 'status', '-e', 'bar'), 1)
1941
746
 
1942
747
    def test_upload_tools(self):
1943
748
        cc_mock = self.addContext(patch('subprocess.check_call'))
1944
 
        client = EnvJujuClient(JujuData(
1945
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path')
1946
 
        with self.bc_context(client, jes='kill-controller'):
1947
 
            with observable_temp_file() as config_file:
1948
 
                with boot_context('bar', client, None, [], None, None, None,
1949
 
                                  None, keep_env=False, upload_tools=True):
1950
 
                    pass
1951
 
        assert_juju_call(self, cc_mock, client, (
1952
 
            'path', '--show-log', 'bootstrap', '--upload-tools',
1953
 
            '--constraints', 'mem=2G', 'bar', 'paas/qux', '--config',
1954
 
            config_file.name, '--default-model', 'bar'), 0)
1955
 
 
1956
 
    def test_upload_tools_non_jes(self):
1957
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
1958
 
        client = EnvJujuClient1X(SimpleEnvironment(
 
749
        client = EnvJujuClient(SimpleEnvironment(
1959
750
            'foo', {'type': 'paas'}), '1.23', 'path')
1960
751
        with self.bc_context(client):
1961
752
            with boot_context('bar', client, None, [], None, None, None, None,
1962
753
                              keep_env=False, upload_tools=True):
1963
754
                pass
1964
755
        assert_juju_call(self, cc_mock, client, (
1965
 
            'path', '--show-log', 'bootstrap', '-e', 'bar', '--upload-tools',
1966
 
            '--constraints', 'mem=2G'), 0)
1967
 
 
1968
 
    def test_calls_update_env_2(self):
1969
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
1970
 
        client = EnvJujuClient(JujuData(
1971
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path')
1972
 
        ue_mock = self.addContext(
1973
 
            patch('deploy_stack.update_env', wraps=update_env))
1974
 
        with self.bc_context(client, jes='kill-controller'):
1975
 
            with observable_temp_file() as config_file:
1976
 
                with boot_context('bar', client, None, [], 'wacky', 'url',
1977
 
                                  'devel', None, keep_env=False,
1978
 
                                  upload_tools=False):
1979
 
                    pass
1980
 
        ue_mock.assert_called_with(
1981
 
            client.env, 'bar', agent_url='url', agent_stream='devel',
1982
 
            series='wacky', bootstrap_host=None, region=None)
1983
 
        assert_juju_call(self, cc_mock, client, (
1984
 
            'path', '--show-log', 'bootstrap', '--constraints', 'mem=2G',
1985
 
            'bar', 'paas/qux', '--config', config_file.name,
1986
 
            '--default-model', 'bar', '--agent-version', '1.23',
1987
 
            '--bootstrap-series', 'wacky'), 0)
1988
 
 
1989
 
    def test_calls_update_env_1(self):
1990
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
1991
 
        client = EnvJujuClient1X(SimpleEnvironment(
1992
 
            'foo', {'type': 'paas'}), '1.23', 'path')
1993
 
        ue_mock = self.addContext(
1994
 
            patch('deploy_stack.update_env', wraps=update_env))
1995
 
        with self.bc_context(client):
1996
 
            with boot_context('bar', client, None, [], 'wacky', 'url', 'devel',
1997
 
                              None, keep_env=False, upload_tools=False):
1998
 
                pass
1999
 
        ue_mock.assert_called_with(
2000
 
            client.env, 'bar', series='wacky', bootstrap_host=None,
2001
 
            agent_url='url', agent_stream='devel', region=None)
2002
 
        assert_juju_call(self, cc_mock, client, (
2003
 
            'path', '--show-log', 'bootstrap', '-e', 'bar',
2004
 
            '--constraints', 'mem=2G'), 0)
2005
 
 
2006
 
    def test_calls_update_env_non_jes(self):
2007
 
        cc_mock = self.addContext(patch('subprocess.check_call'))
2008
 
        client = EnvJujuClient1X(SimpleEnvironment(
2009
 
            'foo', {'type': 'paas'}), '1.23', 'path')
2010
 
        ue_mock = self.addContext(
2011
 
            patch('deploy_stack.update_env', wraps=update_env))
2012
 
        with self.bc_context(client):
2013
 
            with boot_context('bar', client, None, [], 'wacky', 'url', 'devel',
2014
 
                              None, keep_env=False, upload_tools=False):
2015
 
                pass
2016
 
        ue_mock.assert_called_with(
2017
 
            client.env, 'bar', series='wacky', bootstrap_host=None,
2018
 
            agent_url='url', agent_stream='devel', region=None)
2019
 
        assert_juju_call(self, cc_mock, client, (
2020
 
            'path', '--show-log', 'bootstrap', '-e', 'bar',
 
756
            'juju', '--show-log', 'bootstrap', '-e', 'bar', '--upload-tools',
 
757
            '--constraints', 'mem=2G'), 0)
 
758
 
 
759
    def test_calls_update_env(self):
 
760
        cc_mock = self.addContext(patch('subprocess.check_call'))
 
761
        client = EnvJujuClient(SimpleEnvironment(
 
762
            'foo', {'type': 'paas'}), '1.23', 'path')
 
763
        ue_mock = self.addContext(
 
764
            patch('deploy_stack.update_env', wraps=update_env))
 
765
        with self.bc_context(client):
 
766
            with boot_context('bar', client, None, [], 'wacky', 'url', 'devel',
 
767
                              None, keep_env=False, upload_tools=False):
 
768
                pass
 
769
        ue_mock.assert_called_with(
 
770
            client.env, 'bar', series='wacky', bootstrap_host=None,
 
771
            agent_url='url', agent_stream='devel')
 
772
        assert_juju_call(self, cc_mock, client, (
 
773
            'juju', '--show-log', 'bootstrap', '-e', 'bar',
2021
774
            '--constraints', 'mem=2G'), 0)
2022
775
 
2023
776
    def test_with_bootstrap_failure(self):
2025
778
        class FakeException(Exception):
2026
779
            """A sentry exception to be raised by bootstrap."""
2027
780
 
2028
 
        client = EnvJujuClient(JujuData(
2029
 
            'foo', {'type': 'paas'}), '1.23', 'path')
2030
 
        self.addContext(patch('deploy_stack.get_machine_dns_name',
2031
 
                              return_value='foo'))
2032
 
        self.addContext(patch('subprocess.check_call'))
2033
 
        call_mock = self.addContext(patch('subprocess.call', return_value=0))
2034
 
        po_mock = self.addContext(patch(
2035
 
            'subprocess.Popen', autospec=True,
2036
 
            return_value=FakePopen('kill-controller', '', 0)))
2037
 
        self.addContext(patch('deploy_stack.wait_for_port'))
2038
 
        fake_exception = FakeException()
2039
 
        self.addContext(patch.object(client, 'bootstrap',
2040
 
                                     side_effect=fake_exception))
2041
 
        crl_mock = self.addContext(patch('deploy_stack.copy_remote_logs'))
2042
 
        al_mock = self.addContext(patch('deploy_stack.archive_logs'))
2043
 
        le_mock = self.addContext(patch('logging.exception'))
2044
 
        with self.assertRaises(SystemExit):
2045
 
            with boot_context('bar', client, 'baz', [], None, None, None,
2046
 
                              'log_dir', keep_env=False, upload_tools=True):
2047
 
                pass
2048
 
        le_mock.assert_called_once_with(fake_exception)
2049
 
        self.assertEqual(crl_mock.call_count, 1)
2050
 
        call_args = crl_mock.call_args[0]
2051
 
        self.assertIsInstance(call_args[0], _Remote)
2052
 
        self.assertEqual(call_args[0].get_address(), 'baz')
2053
 
        self.assertEqual(call_args[1], 'log_dir')
2054
 
        al_mock.assert_called_once_with('log_dir')
2055
 
        timeout_path = get_timeout_path()
2056
 
        assert_juju_call(self, call_mock, client, (
2057
 
            sys.executable, timeout_path, '600.00', '--',
2058
 
            'path', '--show-log', 'kill-controller', 'bar', '-y'
2059
 
            ), 0)
2060
 
        assert_juju_call(self, call_mock, client, (
2061
 
            sys.executable, timeout_path, '600.00', '--',
2062
 
            'path', '--show-log', 'kill-controller', 'bar', '-y'
2063
 
            ), 1)
2064
 
        self.assertEqual(2, call_mock.call_count)
2065
 
        self.assertEqual(0, po_mock.call_count)
2066
 
 
2067
 
    def test_with_bootstrap_failure_non_jes(self):
2068
 
 
2069
 
        class FakeException(Exception):
2070
 
            """A sentry exception to be raised by bootstrap."""
2071
 
 
2072
 
        client = EnvJujuClient1X(SimpleEnvironment(
2073
 
            'foo', {'type': 'paas'}), '1.23', 'path')
2074
 
        self.addContext(patch('deploy_stack.get_machine_dns_name',
2075
 
                              return_value='foo'))
2076
 
        self.addContext(patch('subprocess.check_call'))
2077
 
        call_mock = self.addContext(patch('subprocess.call', return_value=0))
2078
 
        po_mock = self.addContext(patch('subprocess.Popen', autospec=True,
2079
 
                                        return_value=FakePopen('', '', 0)))
2080
 
        self.addContext(patch('deploy_stack.wait_for_port'))
2081
 
        fake_exception = FakeException()
2082
 
        self.addContext(patch.object(client, 'bootstrap',
2083
 
                                     side_effect=fake_exception))
2084
 
        crl_mock = self.addContext(patch('deploy_stack.copy_remote_logs'))
2085
 
        al_mock = self.addContext(patch('deploy_stack.archive_logs'))
2086
 
        le_mock = self.addContext(patch('logging.exception'))
2087
 
        with self.assertRaises(SystemExit):
2088
 
            with boot_context('bar', client, 'baz', [], None, None, None,
2089
 
                              'log_dir', keep_env=False, upload_tools=True):
2090
 
                pass
2091
 
        le_mock.assert_called_once_with(fake_exception)
2092
 
        self.assertEqual(crl_mock.call_count, 1)
2093
 
        call_args = crl_mock.call_args[0]
2094
 
        self.assertIsInstance(call_args[0], _Remote)
2095
 
        self.assertEqual(call_args[0].get_address(), 'baz')
2096
 
        self.assertEqual(call_args[1], 'log_dir')
2097
 
        al_mock.assert_called_once_with('log_dir')
2098
 
        timeout_path = get_timeout_path()
2099
 
        assert_juju_call(self, call_mock, client, (
2100
 
            sys.executable, timeout_path, '600.00', '--',
2101
 
            'path', '--show-log', 'destroy-environment', 'bar', '-y'
2102
 
            ), 0)
2103
 
        assert_juju_call(self, call_mock, client, (
2104
 
            sys.executable, timeout_path, '600.00', '--',
2105
 
            'path', '--show-log', 'destroy-environment', 'bar', '-y'
2106
 
            ), 1)
2107
 
        self.assertEqual(2, call_mock.call_count)
2108
 
        assert_juju_call(self, po_mock, client, (
2109
 
            'path', '--show-log', 'help', 'commands'), 0)
2110
 
        assert_juju_call(self, po_mock, client, (
2111
 
            'path', '--show-log', 'help', 'commands'), 1)
2112
 
        self.assertEqual(2, po_mock.call_count)
 
781
        client = EnvJujuClient(SimpleEnvironment(
 
782
            'foo', {'type': 'paas'}), '1.23', 'path')
 
783
        self.addContext(patch('deploy_stack.get_machine_dns_name',
 
784
                              return_value='foo'))
 
785
        self.addContext(patch('subprocess.check_call'))
 
786
        self.addContext(patch('subprocess.call'))
 
787
        self.addContext(patch('deploy_stack.wait_for_port'))
 
788
        self.addContext(patch.object(client, 'bootstrap',
 
789
                                     side_effect=FakeException))
 
790
        crl_mock = self.addContext(patch('deploy_stack.copy_remote_logs'))
 
791
        al_mock = self.addContext(patch('deploy_stack.archive_logs'))
 
792
        with self.assertRaises(FakeException):
 
793
            with boot_context('bar', client, 'baz', [], None, None, None,
 
794
                              'log_dir', keep_env=False, upload_tools=True):
 
795
                pass
 
796
        self.assertEqual(crl_mock.call_count, 1)
 
797
        call_args = crl_mock.call_args[0]
 
798
        self.assertIsInstance(call_args[0], _Remote)
 
799
        self.assertEqual(call_args[0].get_address(), 'baz')
 
800
        self.assertEqual(call_args[1], 'log_dir')
 
801
        al_mock.assert_called_once_with('log_dir')
2113
802
 
2114
803
    def test_jes(self):
2115
804
        self.addContext(patch('subprocess.check_call', autospec=True))
2116
 
        client = EnvJujuClient(JujuData(
2117
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.26', 'path')
2118
 
        with self.bc_context(client, 'log_dir', jes=KILL_CONTROLLER):
 
805
        client = EnvJujuClient(SimpleEnvironment(
 
806
            'foo', {'type': 'paas'}), '1.23', 'path')
 
807
        with self.bc_context(client, 'log_dir', jes=True):
2119
808
            with boot_context('bar', client, None, [], None, None, None,
2120
809
                              'log_dir', keep_env=False, upload_tools=False):
2121
810
                pass
2122
811
 
2123
 
    def test_region(self):
2124
 
        self.addContext(patch('subprocess.check_call', autospec=True))
2125
 
        client = EnvJujuClient(JujuData(
2126
 
            'foo', {'type': 'paas'}), '1.23', 'path')
2127
 
        with self.bc_context(client, 'log_dir', jes='kill-controller'):
2128
 
            with boot_context('bar', client, None, [], None, None, None,
2129
 
                              'log_dir', keep_env=False, upload_tools=False,
2130
 
                              region='steve'):
2131
 
                pass
2132
 
        self.assertEqual('steve', client.env.config['region'])
2133
 
 
2134
 
    def test_region_non_jes(self):
2135
 
        self.addContext(patch('subprocess.check_call', autospec=True))
2136
 
        client = EnvJujuClient1X(SimpleEnvironment(
2137
 
            'foo', {'type': 'paas'}), '1.23', 'path')
2138
 
        with self.bc_context(client, 'log_dir'):
2139
 
            with boot_context('bar', client, None, [], None, None, None,
2140
 
                              'log_dir', keep_env=False, upload_tools=False,
2141
 
                              region='steve'):
2142
 
                pass
2143
 
        self.assertEqual('steve', client.env.config['region'])
2144
 
 
2145
 
    def test_status_error_raises(self):
2146
 
        """An error on final show-status propagates so an assess will fail."""
2147
 
        error = subprocess.CalledProcessError(1, ['juju'], '')
2148
 
        effects = [None, None, None, None, None, None, error]
2149
 
        cc_mock = self.addContext(patch('subprocess.check_call', autospec=True,
2150
 
                                        side_effect=effects))
2151
 
        client = EnvJujuClient(JujuData(
2152
 
            'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path')
2153
 
        with self.bc_context(client, 'log_dir', jes='kill-controller'):
2154
 
            with observable_temp_file() as config_file:
2155
 
                with self.assertRaises(subprocess.CalledProcessError) as ctx:
2156
 
                    with boot_context('bar', client, None, [], None, None,
2157
 
                                      None, 'log_dir', keep_env=False,
2158
 
                                      upload_tools=False):
2159
 
                        pass
2160
 
                self.assertIs(ctx.exception, error)
2161
 
        assert_juju_call(self, cc_mock, client, (
2162
 
            'path', '--show-log', 'bootstrap', '--constraints',
2163
 
            'mem=2G', 'bar', 'paas/qux', '--config', config_file.name,
2164
 
            '--default-model', 'bar', '--agent-version', '1.23'), 0)
2165
 
        assert_juju_call(self, cc_mock, client, (
2166
 
            'path', '--show-log', 'list-controllers'), 1)
2167
 
        assert_juju_call(self, cc_mock, client, (
2168
 
            'path', '--show-log', 'list-models', '-c', 'bar'), 2)
2169
 
        assert_juju_call(self, cc_mock, client, (
2170
 
            'path', '--show-log', 'show-status', '-m', 'bar:controller',
2171
 
            '--format', 'yaml'), 3)
2172
 
        assert_juju_call(self, cc_mock, client, (
2173
 
            'path', '--show-log', 'show-status', '-m', 'bar:bar',
2174
 
            '--format', 'yaml'), 4)
2175
 
 
2176
 
 
2177
 
class TestDeployJobParseArgs(FakeHomeTestCase):
 
812
 
 
813
class TestDeployJobParseArgs(TestCase):
2178
814
 
2179
815
    def test_deploy_job_parse_args(self):
2180
 
        args = deploy_job_parse_args(['foo', 'bar/juju', 'baz', 'qux'])
 
816
        args = deploy_job_parse_args(['foo', 'bar', 'baz', 'qux'])
2181
817
        self.assertEqual(args, Namespace(
2182
818
            agent_stream=None,
2183
819
            agent_url=None,
2188
824
            keep_env=False,
2189
825
            logs='baz',
2190
826
            machine=[],
2191
 
            juju_bin='bar/juju',
 
827
            juju_bin='bar',
2192
828
            series=None,
2193
829
            upgrade=False,
2194
830
            verbose=logging.INFO,
2195
831
            upload_tools=False,
2196
832
            with_chaos=0,
2197
833
            jes=False,
2198
 
            region=None,
2199
 
            deadline=None,
 
834
            pre_destroy=False,
2200
835
        ))
2201
836
 
2202
837
    def test_upload_tools(self):
2203
838
        args = deploy_job_parse_args(
2204
 
            ['foo', 'bar/juju', 'baz', 'qux', '--upload-tools'])
 
839
            ['foo', 'bar', 'baz', 'qux', '--upload-tools'])
2205
840
        self.assertEqual(args.upload_tools, True)
2206
841
 
2207
842
    def test_agent_stream(self):
2208
843
        args = deploy_job_parse_args(
2209
 
            ['foo', 'bar/juju', 'baz', 'qux', '--agent-stream', 'wacky'])
 
844
            ['foo', 'bar', 'baz', 'qux', '--agent-stream', 'wacky'])
2210
845
        self.assertEqual('wacky', args.agent_stream)
2211
846
 
2212
847
    def test_jes(self):
2213
848
        args = deploy_job_parse_args(
2214
 
            ['foo', 'bar/juju', 'baz', 'qux', '--jes'])
 
849
            ['foo', 'bar', 'baz', 'qux', '--jes'])
2215
850
        self.assertIs(args.jes, True)