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

« back to all changes in this revision

Viewing changes to test_jujupy.py

  • Committer: Aaron Bentley
  • Date: 2015-01-19 15:32:33 UTC
  • mto: This revision was merged to the branch mainline in revision 804.
  • Revision ID: aaron.bentley@canonical.com-20150119153233-jjcvikwiw1dx2lak
Print error on missing environment.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
__metaclass__ = type
 
2
 
1
3
from contextlib import contextmanager
2
 
import copy
3
4
from datetime import (
4
5
    datetime,
5
6
    timedelta,
6
 
)
7
 
import errno
8
 
import json
9
 
import logging
 
7
    )
10
8
import os
11
 
import socket
 
9
import shutil
12
10
import StringIO
13
11
import subprocess
14
 
import sys
15
 
from tempfile import NamedTemporaryFile
 
12
import tempfile
16
13
from textwrap import dedent
17
 
import types
 
14
from unittest import TestCase
18
15
 
19
16
from mock import (
20
17
    call,
21
18
    MagicMock,
22
 
    Mock,
23
19
    patch,
24
20
)
25
21
import yaml
26
22
 
27
 
from fakejuju import (
28
 
    fake_juju_client,
29
 
    get_user_register_command_info,
30
 
    get_user_register_token,
31
 
)
32
23
from jujuconfig import (
33
24
    get_environments_path,
34
25
    get_jenv_path,
35
26
    NoSuchEnvironment,
36
 
)
 
27
    )
37
28
from jujupy import (
38
 
    BootstrapMismatch,
39
29
    CannotConnectEnv,
40
 
    client_from_config,
41
 
    CONTROLLER,
42
 
    Controller,
 
30
    Environment,
43
31
    EnvJujuClient,
44
 
    EnvJujuClient1X,
45
 
    EnvJujuClient22,
46
 
    EnvJujuClient24,
47
 
    EnvJujuClient25,
48
 
    EnvJujuClient26,
49
 
    EnvJujuClient2A1,
50
 
    EnvJujuClient2A2,
51
 
    EnvJujuClient2B2,
52
 
    EnvJujuClient2B3,
53
 
    EnvJujuClient2B7,
54
 
    EnvJujuClient2B8,
55
 
    EnvJujuClient2B9,
56
32
    ErroredUnit,
57
33
    GroupReporter,
58
 
    get_cache_path,
59
34
    get_local_root,
60
 
    get_machine_dns_name,
61
 
    get_timeout_path,
62
 
    IncompatibleConfigClass,
63
 
    jes_home_path,
64
 
    JESByDefault,
65
 
    JESNotSupported,
66
 
    Juju2Backend,
67
 
    JujuData,
68
 
    JUJU_DEV_FEATURE_FLAGS,
69
 
    KILL_CONTROLLER,
70
 
    Machine,
71
 
    make_safe_config,
72
 
    parse_new_state_server_from_error,
 
35
    JujuClientDevel,
73
36
    SimpleEnvironment,
74
 
    ServiceStatus,
75
 
    SoftDeadlineExceeded,
76
37
    Status,
77
 
    SYSTEM,
78
 
    tear_down,
79
38
    temp_bootstrap_env,
80
 
    _temp_env as temp_env,
81
 
    temp_yaml_file,
 
39
    _temp_env,
82
40
    uniquify_local,
83
 
    UpgradeMongoNotSupported,
84
 
)
85
 
from tests import (
86
 
    TestCase,
87
 
    FakeHomeTestCase,
88
 
)
89
 
from tests.test_assess_resources import make_resource_list
 
41
)
90
42
from utility import (
91
 
    JujuResourceTimeout,
92
43
    scoped_environ,
93
44
    temp_dir,
94
 
)
95
 
 
96
 
 
97
 
__metaclass__ = type
 
45
    )
98
46
 
99
47
 
100
48
def assert_juju_call(test_case, mock_method, client, expected_args,
101
 
                     call_index=None):
 
49
                     call_index=None, assign_stderr=False):
102
50
    if call_index is None:
103
51
        test_case.assertEqual(len(mock_method.mock_calls), 1)
104
52
        call_index = 0
105
53
    empty, args, kwargs = mock_method.mock_calls[call_index]
106
54
    test_case.assertEqual(args, (expected_args,))
 
55
    kwarg_keys = ['env']
 
56
    if assign_stderr:
 
57
        kwarg_keys = ['stderr'] + kwarg_keys
 
58
        test_case.assertEqual(type(kwargs['stderr']), file)
 
59
    test_case.assertEqual(kwargs.keys(), kwarg_keys)
 
60
    bin_dir = os.path.dirname(client.full_path)
 
61
    test_case.assertRegexpMatches(kwargs['env']['PATH'],
 
62
                                  r'^{}\:'.format(bin_dir))
107
63
 
108
64
 
109
65
class TestErroredUnit(TestCase):
113
69
        self.assertEqual('bar is in state baz', str(e))
114
70
 
115
71
 
116
 
class ClientTest(FakeHomeTestCase):
 
72
class TestEnvJujuClient(TestCase):
117
73
 
118
74
    def setUp(self):
119
 
        super(ClientTest, self).setUp()
120
75
        patcher = patch('jujupy.pause')
121
76
        self.addCleanup(patcher.stop)
122
77
        self.pause_mock = patcher.start()
123
78
 
124
 
 
125
 
class CloudSigmaTest:
126
 
 
127
 
    def test__shell_environ_no_flags(self):
128
 
        client = self.client_class(
129
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.25-foobar', 'path')
130
 
        env = client._shell_environ()
131
 
        self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS, ''), '')
132
 
 
133
 
    def test__shell_environ_cloudsigma(self):
134
 
        client = self.client_class(
135
 
            SimpleEnvironment('baz', {'type': 'cloudsigma'}),
136
 
            '1.25-foobar', 'path')
137
 
        env = client._shell_environ()
138
 
        self.assertTrue('cloudsigma' in env[JUJU_DEV_FEATURE_FLAGS].split(","))
139
 
 
140
 
    def test__shell_environ_juju_home(self):
141
 
        client = self.client_class(
142
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.25-foobar', 'path',
143
 
            'asdf')
144
 
        env = client._shell_environ()
145
 
        self.assertEqual(env['JUJU_HOME'], 'asdf')
146
 
 
147
 
 
148
 
class TestTempYamlFile(TestCase):
149
 
 
150
 
    def test_temp_yaml_file(self):
151
 
        with temp_yaml_file({'foo': 'bar'}) as yaml_file:
152
 
            with open(yaml_file) as f:
153
 
                self.assertEqual({'foo': 'bar'}, yaml.safe_load(f))
154
 
 
155
 
 
156
 
class TestJuju2Backend(TestCase):
157
 
 
158
 
    test_environ = {'PATH': 'foo:bar'}
159
 
 
160
 
    def test_juju2_backend(self):
161
 
        backend = Juju2Backend('/bin/path', '2.0', set(), False)
162
 
        self.assertEqual('/bin/path', backend.full_path)
163
 
        self.assertEqual('2.0', backend.version)
164
 
 
165
 
    def test_clone_retains_soft_deadline(self):
166
 
        soft_deadline = object()
167
 
        backend = Juju2Backend('/bin/path', '2.0', feature_flags=set(),
168
 
                               debug=False, soft_deadline=soft_deadline)
169
 
        cloned = backend.clone(full_path=None, version=None, debug=None,
170
 
                               feature_flags=None)
171
 
        self.assertIsNot(cloned, backend)
172
 
        self.assertIs(soft_deadline, cloned.soft_deadline)
173
 
 
174
 
    def test__check_timeouts(self):
175
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
176
 
                               soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
177
 
        with patch('jujupy.Juju2Backend._now',
178
 
                   return_value=backend.soft_deadline):
179
 
            with backend._check_timeouts():
180
 
                pass
181
 
        now = backend.soft_deadline + timedelta(seconds=1)
182
 
        with patch('jujupy.Juju2Backend._now', return_value=now):
183
 
            with self.assertRaisesRegexp(SoftDeadlineExceeded,
184
 
                                         'Operation exceeded deadline.'):
185
 
                with backend._check_timeouts():
186
 
                    pass
187
 
 
188
 
    def test__check_timeouts_no_deadline(self):
189
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
190
 
                               soft_deadline=None)
191
 
        now = datetime(2015, 1, 2, 3, 4, 6)
192
 
        with patch('jujupy.Juju2Backend._now', return_value=now):
193
 
            with backend._check_timeouts():
194
 
                pass
195
 
 
196
 
    def test_ignore_soft_deadline_check_timeouts(self):
197
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
198
 
                               soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
199
 
        now = backend.soft_deadline + timedelta(seconds=1)
200
 
        with patch('jujupy.Juju2Backend._now', return_value=now):
201
 
            with backend.ignore_soft_deadline():
202
 
                with backend._check_timeouts():
203
 
                    pass
204
 
            with self.assertRaisesRegexp(SoftDeadlineExceeded,
205
 
                                         'Operation exceeded deadline.'):
206
 
                with backend._check_timeouts():
207
 
                    pass
208
 
 
209
 
    def test_juju_checks_timeouts(self):
210
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
211
 
                               soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
212
 
        with patch('subprocess.check_call'):
213
 
            with patch('jujupy.Juju2Backend._now',
214
 
                       return_value=backend.soft_deadline):
215
 
                backend.juju('cmd', ('args',), [], 'home')
216
 
            now = backend.soft_deadline + timedelta(seconds=1)
217
 
            with patch('jujupy.Juju2Backend._now', return_value=now):
218
 
                with self.assertRaisesRegexp(SoftDeadlineExceeded,
219
 
                                             'Operation exceeded deadline.'):
220
 
                    backend.juju('cmd', ('args',), [], 'home')
221
 
 
222
 
    def test_juju_async_checks_timeouts(self):
223
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
224
 
                               soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
225
 
        with patch('subprocess.Popen') as mock_popen:
226
 
            mock_popen.return_value.wait.return_value = 0
227
 
            with patch('jujupy.Juju2Backend._now',
228
 
                       return_value=backend.soft_deadline):
229
 
                with backend.juju_async('cmd', ('args',), [], 'home'):
230
 
                    pass
231
 
            now = backend.soft_deadline + timedelta(seconds=1)
232
 
            with patch('jujupy.Juju2Backend._now', return_value=now):
233
 
                with self.assertRaisesRegexp(SoftDeadlineExceeded,
234
 
                                             'Operation exceeded deadline.'):
235
 
                    with backend.juju_async('cmd', ('args',), [], 'home'):
236
 
                        pass
237
 
 
238
 
    def test_get_juju_output_checks_timeouts(self):
239
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
240
 
                               soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
241
 
        with patch('subprocess.Popen') as mock_popen:
242
 
            mock_popen.return_value.returncode = 0
243
 
            mock_popen.return_value.communicate.return_value = ('', '')
244
 
            with patch('jujupy.Juju2Backend._now',
245
 
                       return_value=backend.soft_deadline):
246
 
                backend.get_juju_output('cmd', ('args',), [], 'home')
247
 
            now = backend.soft_deadline + timedelta(seconds=1)
248
 
            with patch('jujupy.Juju2Backend._now', return_value=now):
249
 
                with self.assertRaisesRegexp(SoftDeadlineExceeded,
250
 
                                             'Operation exceeded deadline.'):
251
 
                    backend.get_juju_output('cmd', ('args',), [], 'home')
252
 
 
253
 
 
254
 
class TestEnvJujuClient26(ClientTest, CloudSigmaTest):
255
 
 
256
 
    client_class = EnvJujuClient26
257
 
 
258
 
    def test_enable_jes_already_supported(self):
259
 
        client = self.client_class(
260
 
            SimpleEnvironment('baz', {}),
261
 
            '1.26-foobar', 'path')
262
 
        fake_popen = FakePopen(CONTROLLER, '', 0)
263
 
        with patch('subprocess.Popen', autospec=True,
264
 
                   return_value=fake_popen) as po_mock:
265
 
            with self.assertRaises(JESByDefault):
266
 
                client.enable_jes()
267
 
        self.assertNotIn('jes', client.feature_flags)
268
 
        assert_juju_call(
269
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'))
270
 
 
271
 
    def test_enable_jes_unsupported(self):
272
 
        client = self.client_class(
273
 
            SimpleEnvironment('baz', {}),
274
 
            '1.24-foobar', 'path')
275
 
        with patch('subprocess.Popen', autospec=True,
276
 
                   return_value=FakePopen('', '', 0)) as po_mock:
277
 
            with self.assertRaises(JESNotSupported):
278
 
                client.enable_jes()
279
 
        self.assertNotIn('jes', client.feature_flags)
280
 
        assert_juju_call(
281
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
282
 
            0)
283
 
        assert_juju_call(
284
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
285
 
            1)
286
 
        self.assertEqual(po_mock.call_count, 2)
287
 
 
288
 
    def test_enable_jes_requires_flag(self):
289
 
        client = self.client_class(
290
 
            SimpleEnvironment('baz', {}),
291
 
            '1.25-foobar', 'path')
292
 
        # The help output will change when the jes feature flag is set.
293
 
        with patch('subprocess.Popen', autospec=True, side_effect=[
294
 
                FakePopen('', '', 0),
295
 
                FakePopen(SYSTEM, '', 0)]) as po_mock:
296
 
            client.enable_jes()
297
 
        self.assertIn('jes', client.feature_flags)
298
 
        assert_juju_call(
299
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
300
 
            0)
301
 
        # GZ 2015-10-26: Should assert that env has feature flag at call time.
302
 
        assert_juju_call(
303
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
304
 
            1)
305
 
        self.assertEqual(po_mock.call_count, 2)
306
 
 
307
 
    def test_disable_jes(self):
308
 
        client = self.client_class(
309
 
            SimpleEnvironment('baz', {}),
310
 
            '1.25-foobar', 'path')
311
 
        client.feature_flags.add('jes')
312
 
        client.disable_jes()
313
 
        self.assertNotIn('jes', client.feature_flags)
314
 
 
315
 
    def test__shell_environ_jes(self):
316
 
        client = self.client_class(
317
 
            SimpleEnvironment('baz', {}),
318
 
            '1.25-foobar', 'path')
319
 
        client.feature_flags.add('jes')
320
 
        env = client._shell_environ()
321
 
        self.assertIn('jes', env[JUJU_DEV_FEATURE_FLAGS].split(","))
322
 
 
323
 
    def test__shell_environ_jes_cloudsigma(self):
324
 
        client = self.client_class(
325
 
            SimpleEnvironment('baz', {'type': 'cloudsigma'}),
326
 
            '1.25-foobar', 'path')
327
 
        client.feature_flags.add('jes')
328
 
        env = client._shell_environ()
329
 
        flags = env[JUJU_DEV_FEATURE_FLAGS].split(",")
330
 
        self.assertItemsEqual(['cloudsigma', 'jes'], flags)
331
 
 
332
 
    def test_clone_unchanged(self):
333
 
        client1 = self.client_class(
334
 
            SimpleEnvironment('foo'), '1.27', 'full/path', debug=True)
335
 
        client2 = client1.clone()
336
 
        self.assertIsNot(client1, client2)
337
 
        self.assertIs(type(client1), type(client2))
338
 
        self.assertIs(client1.env, client2.env)
339
 
        self.assertEqual(client1.version, client2.version)
340
 
        self.assertEqual(client1.full_path, client2.full_path)
341
 
        self.assertIs(client1.debug, client2.debug)
342
 
        self.assertEqual(client1._backend, client2._backend)
343
 
 
344
 
    def test_clone_changed(self):
345
 
        client1 = self.client_class(
346
 
            SimpleEnvironment('foo'), '1.27', 'full/path', debug=True)
347
 
        env2 = SimpleEnvironment('bar')
348
 
        client2 = client1.clone(env2, '1.28', 'other/path', debug=False,
349
 
                                cls=EnvJujuClient1X)
350
 
        self.assertIs(EnvJujuClient1X, type(client2))
351
 
        self.assertIs(env2, client2.env)
352
 
        self.assertEqual('1.28', client2.version)
353
 
        self.assertEqual('other/path', client2.full_path)
354
 
        self.assertIs(False, client2.debug)
355
 
 
356
 
    def test_clone_defaults(self):
357
 
        client1 = self.client_class(
358
 
            SimpleEnvironment('foo'), '1.27', 'full/path', debug=True)
359
 
        client2 = client1.clone()
360
 
        self.assertIsNot(client1, client2)
361
 
        self.assertIs(self.client_class, type(client2))
362
 
        self.assertEqual(set(), client2.feature_flags)
363
 
 
364
 
    def test_clone_enabled(self):
365
 
        client1 = self.client_class(
366
 
            SimpleEnvironment('foo'), '1.27', 'full/path', debug=True)
367
 
        client1.enable_feature('jes')
368
 
        client1.enable_feature('address-allocation')
369
 
        client2 = client1.clone()
370
 
        self.assertIsNot(client1, client2)
371
 
        self.assertIs(self.client_class, type(client2))
372
 
        self.assertEqual(
373
 
            set(['jes', 'address-allocation']),
374
 
            client2.feature_flags)
375
 
 
376
 
    def test_clone_old_feature(self):
377
 
        client1 = self.client_class(
378
 
            SimpleEnvironment('foo'), '1.27', 'full/path', debug=True)
379
 
        client1.enable_feature('actions')
380
 
        client1.enable_feature('address-allocation')
381
 
        client2 = client1.clone()
382
 
        self.assertIsNot(client1, client2)
383
 
        self.assertIs(self.client_class, type(client2))
384
 
        self.assertEqual(set(['address-allocation']), client2.feature_flags)
385
 
 
386
 
 
387
 
class TestEnvJujuClient25(TestEnvJujuClient26):
388
 
 
389
 
    client_class = EnvJujuClient25
390
 
 
391
 
 
392
 
class TestEnvJujuClient22(ClientTest):
393
 
 
394
 
    client_class = EnvJujuClient22
395
 
 
396
 
    def test__shell_environ(self):
397
 
        client = self.client_class(
398
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path')
399
 
        env = client._shell_environ()
400
 
        self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS), 'actions')
401
 
 
402
 
    def test__shell_environ_juju_home(self):
403
 
        client = self.client_class(
404
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.22-foobar', 'path',
405
 
            'asdf')
406
 
        env = client._shell_environ()
407
 
        self.assertEqual(env['JUJU_HOME'], 'asdf')
408
 
 
409
 
 
410
 
class TestEnvJujuClient24(ClientTest, CloudSigmaTest):
411
 
 
412
 
    client_class = EnvJujuClient24
413
 
 
414
 
    def test_no_jes(self):
415
 
        client = self.client_class(
416
 
            SimpleEnvironment('baz', {'type': 'cloudsigma'}),
417
 
            '1.25-foobar', 'path')
418
 
        with self.assertRaises(JESNotSupported):
419
 
            client.enable_jes()
420
 
        client._use_jes = True
421
 
        env = client._shell_environ()
422
 
        self.assertNotIn('jes', env[JUJU_DEV_FEATURE_FLAGS].split(","))
423
 
 
424
 
    def test_add_ssh_machines(self):
425
 
        client = self.client_class(SimpleEnvironment('foo', {}), None, 'juju')
426
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
427
 
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
428
 
        assert_juju_call(self, cc_mock, client, (
429
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 0)
430
 
        assert_juju_call(self, cc_mock, client, (
431
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-bar'), 1)
432
 
        assert_juju_call(self, cc_mock, client, (
433
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-baz'), 2)
434
 
        self.assertEqual(cc_mock.call_count, 3)
435
 
 
436
 
    def test_add_ssh_machines_no_retry(self):
437
 
        client = self.client_class(SimpleEnvironment('foo', {}), None, 'juju')
438
 
        with patch('subprocess.check_call', autospec=True,
439
 
                   side_effect=[subprocess.CalledProcessError(None, None),
440
 
                                None, None, None]) as cc_mock:
441
 
            with self.assertRaises(subprocess.CalledProcessError):
442
 
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
443
 
        assert_juju_call(self, cc_mock, client, (
444
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'))
445
 
 
446
 
 
447
 
class TestTearDown(TestCase):
448
 
 
449
 
    def test_tear_down_no_jes(self):
450
 
        client = MagicMock()
451
 
        client.destroy_environment.return_value = 0
452
 
        tear_down(client, False)
453
 
        client.destroy_environment.assert_called_once_with(force=False)
454
 
        self.assertEqual(0, client.kill_controller.call_count)
455
 
        self.assertEqual(0, client.disable_jes.call_count)
456
 
 
457
 
    def test_tear_down_no_jes_exception(self):
458
 
        client = MagicMock()
459
 
        client.destroy_environment.side_effect = [1, 0]
460
 
        tear_down(client, False)
461
 
        self.assertEqual(
462
 
            client.destroy_environment.mock_calls,
463
 
            [call(force=False), call(force=True)])
464
 
        self.assertEqual(0, client.kill_controller.call_count)
465
 
        self.assertEqual(0, client.disable_jes.call_count)
466
 
 
467
 
    def test_tear_down_jes(self):
468
 
        client = MagicMock()
469
 
        tear_down(client, True)
470
 
        client.kill_controller.assert_called_once_with()
471
 
        self.assertEqual(0, client.destroy_environment.call_count)
472
 
        self.assertEqual(0, client.enable_jes.call_count)
473
 
        self.assertEqual(0, client.disable_jes.call_count)
474
 
 
475
 
    def test_tear_down_try_jes(self):
476
 
 
477
 
        def check_jes():
478
 
            client.enable_jes.assert_called_once_with()
479
 
            self.assertEqual(0, client.disable_jes.call_count)
480
 
 
481
 
        client = MagicMock()
482
 
        client.kill_controller.side_effect = check_jes
483
 
 
484
 
        tear_down(client, jes_enabled=False, try_jes=True)
485
 
        client.kill_controller.assert_called_once_with()
486
 
        client.disable_jes.assert_called_once_with()
487
 
 
488
 
    def test_tear_down_jes_try_jes(self):
489
 
        client = MagicMock()
490
 
        tear_down(client, jes_enabled=True, try_jes=True)
491
 
        client.kill_controller.assert_called_once_with()
492
 
        self.assertEqual(0, client.destroy_environment.call_count)
493
 
        self.assertEqual(0, client.enable_jes.call_count)
494
 
        self.assertEqual(0, client.disable_jes.call_count)
495
 
 
496
 
    def test_tear_down_try_jes_not_supported(self):
497
 
 
498
 
        def check_jes(force=True):
499
 
            client.enable_jes.assert_called_once_with()
500
 
            return 0
501
 
 
502
 
        client = MagicMock()
503
 
        client.enable_jes.side_effect = JESNotSupported
504
 
        client.destroy_environment.side_effect = check_jes
505
 
 
506
 
        tear_down(client, jes_enabled=False, try_jes=True)
507
 
        client.destroy_environment.assert_called_once_with(force=False)
508
 
        self.assertEqual(0, client.disable_jes.call_count)
509
 
 
510
 
 
511
 
class FakePopen(object):
512
 
 
513
 
    def __init__(self, out, err, returncode):
514
 
        self._out = out
515
 
        self._err = err
516
 
        self._code = returncode
517
 
 
518
 
    def communicate(self):
519
 
        self.returncode = self._code
520
 
        return self._out, self._err
521
 
 
522
 
    def poll(self):
523
 
        return self._code
524
 
 
525
 
 
526
 
@contextmanager
527
 
def observable_temp_file():
528
 
    temporary_file = NamedTemporaryFile(delete=False)
529
 
    try:
530
 
        with temporary_file as temp_file:
531
 
            with patch('jujupy.NamedTemporaryFile',
532
 
                       return_value=temp_file):
533
 
                with patch.object(temp_file, '__exit__'):
534
 
                    yield temp_file
535
 
    finally:
536
 
        try:
537
 
            os.unlink(temporary_file.name)
538
 
        except OSError as e:
539
 
            # File may have already been deleted, e.g. by temp_yaml_file.
540
 
            if e.errno != errno.ENOENT:
541
 
                raise
542
 
 
543
 
 
544
 
class TestClientFromConfig(ClientTest):
545
 
 
546
 
    @patch.object(JujuData, 'from_config', return_value=JujuData('', {}))
547
 
    @patch.object(SimpleEnvironment, 'from_config',
548
 
                  return_value=SimpleEnvironment('', {}))
549
 
    @patch.object(EnvJujuClient, 'get_full_path', return_value='fake-path')
550
 
    def test_from_config(self, gfp_mock, se_fc_mock, jd_fc_mock):
 
79
    def test_get_version(self):
 
80
        value = ' 5.6 \n'
 
81
        with patch('subprocess.check_output', return_value=value) as vsn:
 
82
            version = EnvJujuClient.get_version()
 
83
        self.assertEqual('5.6', version)
 
84
        vsn.assert_called_with(('juju', '--version'))
 
85
 
 
86
    def test_get_version_path(self):
 
87
        with patch('subprocess.check_output', return_value=' 4.3') as vsn:
 
88
            EnvJujuClient.get_version('foo/bar/baz')
 
89
        vsn.assert_called_once_with(('foo/bar/baz', '--version'))
 
90
 
 
91
    def test_get_matching_agent_version(self):
 
92
        client = EnvJujuClient(SimpleEnvironment(None, {'type': 'local'}),
 
93
                               '1.23-series-arch', None)
 
94
        self.assertEqual('1.23.1', client.get_matching_agent_version())
 
95
        self.assertEqual('1.23', client.get_matching_agent_version(
 
96
                         no_build=True))
 
97
        client.version = '1.20-beta1-series-arch'
 
98
        self.assertEqual('1.20-beta1.1', client.get_matching_agent_version())
 
99
 
 
100
    def test_upgrade_juju_nonlocal(self):
 
101
        client = EnvJujuClient(
 
102
            SimpleEnvironment('foo', {'type': 'nonlocal'}), '1.234-76', None)
 
103
        with patch.object(client, 'juju') as juju_mock:
 
104
            client.upgrade_juju()
 
105
        juju_mock.assert_called_with(
 
106
            'upgrade-juju', ('--version', '1.234'))
 
107
 
 
108
    def test_upgrade_juju_local(self):
 
109
        client = EnvJujuClient(
 
110
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
 
111
        with patch.object(client, 'juju') as juju_mock:
 
112
            client.upgrade_juju()
 
113
        juju_mock.assert_called_with(
 
114
            'upgrade-juju', ('--version', '1.234', '--upload-tools',))
 
115
 
 
116
    def test_upgrade_juju_no_force_version(self):
 
117
        client = EnvJujuClient(
 
118
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
 
119
        with patch.object(client, 'juju') as juju_mock:
 
120
            client.upgrade_juju(force_version=False)
 
121
        juju_mock.assert_called_with(
 
122
            'upgrade-juju', ('--upload-tools',))
 
123
 
 
124
    def test_by_version(self):
551
125
        def juju_cmd_iterator():
552
126
            yield '1.17'
553
127
            yield '1.16'
554
128
            yield '1.16.1'
555
129
            yield '1.15'
556
 
            yield '1.22.1'
557
 
            yield '1.24-alpha1'
558
 
            yield '1.24.7'
559
 
            yield '1.25.1'
560
 
            yield '1.26.1'
561
 
            yield '1.27.1'
562
 
            yield '2.0-alpha1'
563
 
            yield '2.0-alpha2'
564
 
            yield '2.0-alpha3'
565
 
            yield '2.0-beta1'
566
 
            yield '2.0-beta2'
567
 
            yield '2.0-beta3'
568
 
            yield '2.0-beta4'
569
 
            yield '2.0-beta5'
570
 
            yield '2.0-beta6'
571
 
            yield '2.0-beta7'
572
 
            yield '2.0-beta8'
573
 
            yield '2.0-beta9'
574
 
            yield '2.0-beta10'
575
 
            yield '2.0-beta11'
576
 
            yield '2.0-beta12'
577
 
            yield '2.0-beta13'
578
 
            yield '2.0-beta14'
579
 
            yield '2.0-beta15'
580
 
            yield '2.0-delta1'
581
130
 
582
131
        context = patch.object(
583
132
            EnvJujuClient, 'get_version',
584
133
            side_effect=juju_cmd_iterator().send)
585
134
        with context:
586
 
            self.assertIs(EnvJujuClient1X,
587
 
                          type(client_from_config('foo', None)))
 
135
            self.assertIs(EnvJujuClient,
 
136
                          type(EnvJujuClient.by_version(None)))
588
137
            with self.assertRaisesRegexp(Exception, 'Unsupported juju: 1.16'):
589
 
                client_from_config('foo', None)
 
138
                EnvJujuClient.by_version(None)
590
139
            with self.assertRaisesRegexp(Exception,
591
140
                                         'Unsupported juju: 1.16.1'):
592
 
                client_from_config('foo', None)
593
 
 
594
 
            def test_fc(version, cls):
595
 
                client = client_from_config('foo', None)
596
 
                if isinstance(client, EnvJujuClient2A2):
597
 
                    self.assertEqual(se_fc_mock.return_value, client.env)
598
 
                else:
599
 
                    self.assertEqual(jd_fc_mock.return_value, client.env)
600
 
                self.assertIs(cls, type(client))
601
 
                self.assertEqual(version, client.version)
602
 
 
603
 
            test_fc('1.15', EnvJujuClient1X)
604
 
            test_fc('1.22.1', EnvJujuClient22)
605
 
            test_fc('1.24-alpha1', EnvJujuClient24)
606
 
            test_fc('1.24.7', EnvJujuClient24)
607
 
            test_fc('1.25.1', EnvJujuClient25)
608
 
            test_fc('1.26.1', EnvJujuClient26)
609
 
            test_fc('1.27.1', EnvJujuClient1X)
610
 
            test_fc('2.0-alpha1', EnvJujuClient2A1)
611
 
            test_fc('2.0-alpha2', EnvJujuClient2A2)
612
 
            test_fc('2.0-alpha3', EnvJujuClient2B2)
613
 
            test_fc('2.0-beta1', EnvJujuClient2B2)
614
 
            test_fc('2.0-beta2', EnvJujuClient2B2)
615
 
            test_fc('2.0-beta3', EnvJujuClient2B3)
616
 
            test_fc('2.0-beta4', EnvJujuClient2B3)
617
 
            test_fc('2.0-beta5', EnvJujuClient2B3)
618
 
            test_fc('2.0-beta6', EnvJujuClient2B3)
619
 
            test_fc('2.0-beta7', EnvJujuClient2B7)
620
 
            test_fc('2.0-beta8', EnvJujuClient2B8)
621
 
            test_fc('2.0-beta9', EnvJujuClient2B9)
622
 
            test_fc('2.0-beta10', EnvJujuClient2B9)
623
 
            test_fc('2.0-beta11', EnvJujuClient2B9)
624
 
            test_fc('2.0-beta12', EnvJujuClient2B9)
625
 
            test_fc('2.0-beta13', EnvJujuClient2B9)
626
 
            test_fc('2.0-beta14', EnvJujuClient2B9)
627
 
            test_fc('2.0-beta15', EnvJujuClient)
628
 
            test_fc('2.0-delta1', EnvJujuClient)
629
 
            with self.assertRaises(StopIteration):
630
 
                client_from_config('foo', None)
631
 
 
632
 
    def test_client_from_config_path(self):
 
141
                EnvJujuClient.by_version(None)
 
142
            client = EnvJujuClient.by_version(None)
 
143
            self.assertIs(EnvJujuClient, type(client))
 
144
            self.assertEqual('1.15', client.version)
 
145
 
 
146
    def test_by_version_path(self):
633
147
        with patch('subprocess.check_output', return_value=' 4.3') as vsn:
634
 
            with patch.object(JujuData, 'from_config'):
635
 
                client = client_from_config('foo', 'foo/bar/qux')
 
148
            client = EnvJujuClient.by_version(None, 'foo/bar/qux')
636
149
        vsn.assert_called_once_with(('foo/bar/qux', '--version'))
637
150
        self.assertNotEqual(client.full_path, 'foo/bar/qux')
638
151
        self.assertEqual(client.full_path, os.path.abspath('foo/bar/qux'))
639
152
 
640
 
    def test_client_from_config_keep_home(self):
641
 
        env = JujuData({}, juju_home='/foo/bar')
642
 
        with patch('subprocess.check_output', return_value='2.0-alpha3-a-b'):
643
 
            with patch.object(JujuData, 'from_config',
644
 
                              side_effect=lambda x: JujuData(x, {})):
645
 
                client_from_config('foo', 'foo/bar/qux')
646
 
        self.assertEqual('/foo/bar', env.juju_home)
647
 
 
648
 
    def test_client_from_config_deadline(self):
649
 
        deadline = datetime(2012, 11, 10, 9, 8, 7)
650
 
        with patch('subprocess.check_output', return_value='2.0-alpha3-a-b'):
651
 
            with patch.object(JujuData, 'from_config',
652
 
                              side_effect=lambda x: JujuData(x, {})):
653
 
                client = client_from_config(
654
 
                    'foo', 'foo/bar/qux', soft_deadline=deadline)
655
 
        self.assertEqual(client._backend.soft_deadline, deadline)
656
 
 
657
 
 
658
 
@contextmanager
659
 
def client_past_deadline():
660
 
    client = EnvJujuClient(JujuData('local', juju_home=''), None, None)
661
 
    soft_deadline = datetime(2015, 1, 2, 3, 4, 6)
662
 
    now = soft_deadline + timedelta(seconds=1)
663
 
    client._backend.soft_deadline = soft_deadline
664
 
    with patch.object(client._backend, '_now', return_value=now,
665
 
                      autospec=True):
666
 
        yield client
667
 
 
668
 
 
669
 
class TestEnvJujuClient(ClientTest):
670
 
 
671
 
    def test_no_duplicate_env(self):
672
 
        env = JujuData('foo', {})
673
 
        client = EnvJujuClient(env, '1.25', 'full_path')
674
 
        self.assertIs(env, client.env)
675
 
 
676
 
    def test_convert_to_juju_data(self):
677
 
        env = SimpleEnvironment('foo', {'type': 'bar'}, 'baz')
678
 
        with patch.object(JujuData, 'load_yaml'):
679
 
            client = EnvJujuClient(env, '1.25', 'full_path')
680
 
            client.env.load_yaml.assert_called_once_with()
681
 
        self.assertIsInstance(client.env, JujuData)
682
 
        self.assertEqual(client.env.environment, 'foo')
683
 
        self.assertEqual(client.env.config, {'type': 'bar'})
684
 
        self.assertEqual(client.env.juju_home, 'baz')
685
 
 
686
 
    def test_get_version(self):
687
 
        value = ' 5.6 \n'
688
 
        with patch('subprocess.check_output', return_value=value) as vsn:
689
 
            version = EnvJujuClient.get_version()
690
 
        self.assertEqual('5.6', version)
691
 
        vsn.assert_called_with(('juju', '--version'))
692
 
 
693
 
    def test_get_version_path(self):
694
 
        with patch('subprocess.check_output', return_value=' 4.3') as vsn:
695
 
            EnvJujuClient.get_version('foo/bar/baz')
696
 
        vsn.assert_called_once_with(('foo/bar/baz', '--version'))
697
 
 
698
 
    def test_get_matching_agent_version(self):
699
 
        client = EnvJujuClient(
700
 
            JujuData(None, {'type': 'local'}, juju_home='foo'),
701
 
            '1.23-series-arch', None)
702
 
        self.assertEqual('1.23.1', client.get_matching_agent_version())
703
 
        self.assertEqual('1.23', client.get_matching_agent_version(
704
 
                         no_build=True))
705
 
        client = client.clone(version='1.20-beta1-series-arch')
706
 
        self.assertEqual('1.20-beta1.1', client.get_matching_agent_version())
707
 
 
708
 
    def test_upgrade_juju_nonlocal(self):
709
 
        client = EnvJujuClient(
710
 
            JujuData('foo', {'type': 'nonlocal'}), '2.0-betaX', None)
711
 
        with patch.object(client, '_upgrade_juju') as juju_mock:
712
 
            client.upgrade_juju()
713
 
        juju_mock.assert_called_with(('--agent-version', '2.0'))
714
 
 
715
 
    def test_upgrade_juju_local(self):
716
 
        client = EnvJujuClient(
717
 
            JujuData('foo', {'type': 'local'}), '2.0-betaX', None)
718
 
        with patch.object(client, '_upgrade_juju') as juju_mock:
719
 
            client.upgrade_juju()
720
 
        juju_mock.assert_called_with(('--agent-version', '2.0',))
721
 
 
722
 
    def test_upgrade_juju_no_force_version(self):
723
 
        client = EnvJujuClient(
724
 
            JujuData('foo', {'type': 'local'}), '2.0-betaX', None)
725
 
        with patch.object(client, '_upgrade_juju') as juju_mock:
726
 
            client.upgrade_juju(force_version=False)
727
 
        juju_mock.assert_called_with(())
728
 
 
729
 
    def test_clone_unchanged(self):
730
 
        client1 = EnvJujuClient(JujuData('foo'), '1.27', 'full/path',
731
 
                                debug=True)
732
 
        client2 = client1.clone()
733
 
        self.assertIsNot(client1, client2)
734
 
        self.assertIs(type(client1), type(client2))
735
 
        self.assertIs(client1.env, client2.env)
736
 
        self.assertEqual(client1.version, client2.version)
737
 
        self.assertEqual(client1.full_path, client2.full_path)
738
 
        self.assertIs(client1.debug, client2.debug)
739
 
        self.assertEqual(client1.feature_flags, client2.feature_flags)
740
 
        self.assertEqual(client1._backend, client2._backend)
741
 
 
742
 
    def test_clone_changed(self):
743
 
        client1 = EnvJujuClient(JujuData('foo'), '1.27', 'full/path',
744
 
                                debug=True)
745
 
        env2 = SimpleEnvironment('bar')
746
 
        client2 = client1.clone(env2, '1.28', 'other/path', debug=False,
747
 
                                cls=EnvJujuClient1X)
748
 
        self.assertIs(EnvJujuClient1X, type(client2))
749
 
        self.assertIs(env2, client2.env)
750
 
        self.assertEqual('1.28', client2.version)
751
 
        self.assertEqual('other/path', client2.full_path)
752
 
        self.assertIs(False, client2.debug)
753
 
        self.assertEqual(client1.feature_flags, client2.feature_flags)
754
 
 
755
 
    def test_get_cache_path(self):
756
 
        client = EnvJujuClient(JujuData('foo', juju_home='/foo/'),
757
 
                               '1.27', 'full/path', debug=True)
758
 
        self.assertEqual('/foo/models/cache.yaml',
759
 
                         client.get_cache_path())
760
 
 
761
 
    def test_full_args(self):
762
 
        env = JujuData('foo')
763
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
764
 
        full = client._full_args('bar', False, ('baz', 'qux'))
765
 
        self.assertEqual(('bin', '--show-log', 'bar', '-m', 'foo:foo', 'baz',
766
 
                          'qux'), full)
767
 
        full = client._full_args('bar', True, ('baz', 'qux'))
768
 
        self.assertEqual((
769
 
            'bin', '--show-log', 'bar', '-m', 'foo:foo', 'baz', 'qux'), full)
770
 
        full = client._full_args('bar', True, ('baz', 'qux'), controller=True)
771
 
        self.assertEqual(
772
 
            ('bin', '--show-log', 'bar', '-m', 'foo:controller', 'baz', 'qux'),
773
 
            full)
774
 
        client.env = None
775
 
        full = client._full_args('bar', False, ('baz', 'qux'))
776
 
        self.assertEqual(('bin', '--show-log', 'bar', 'baz', 'qux'), full)
777
 
 
778
 
    def test_full_args_debug(self):
779
 
        env = JujuData('foo')
780
 
        client = EnvJujuClient(env, None, 'my/juju/bin', debug=True)
781
 
        full = client._full_args('bar', False, ('baz', 'qux'))
782
 
        self.assertEqual((
783
 
            'bin', '--debug', 'bar', '-m', 'foo:foo', 'baz', 'qux'), full)
784
 
 
785
 
    def test_full_args_action(self):
786
 
        env = JujuData('foo')
787
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
788
 
        full = client._full_args('action bar', False, ('baz', 'qux'))
789
 
        self.assertEqual(
790
 
            ('bin', '--show-log', 'action', 'bar', '-m', 'foo:foo',
791
 
             'baz', 'qux'),
792
 
            full)
793
 
 
794
 
    def test_full_args_controller(self):
795
 
        env = JujuData('foo')
796
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
797
 
        with patch.object(client, 'get_controller_model_name',
798
 
                          return_value='controller') as gamn_mock:
799
 
            full = client._full_args('bar', False, ('baz', 'qux'),
800
 
                                     controller=True)
801
 
        self.assertEqual((
802
 
            'bin', '--show-log', 'bar', '-m', 'foo:controller', 'baz', 'qux'),
803
 
            full)
804
 
        gamn_mock.assert_called_once_with()
805
 
 
806
 
    def test_make_model_config_prefers_agent_metadata_url(self):
807
 
        env = JujuData('qux', {
808
 
            'agent-metadata-url': 'foo',
809
 
            'tools-metadata-url': 'bar',
810
 
            'type': 'baz',
811
 
            })
812
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
813
 
        self.assertEqual({
814
 
            'agent-metadata-url': 'foo',
815
 
            'test-mode': True,
816
 
            }, client.make_model_config())
817
 
 
818
 
    def test__bootstrap_config(self):
819
 
        env = JujuData('foo', {
820
 
            'access-key': 'foo',
821
 
            'admin-secret': 'foo',
822
 
            'agent-stream': 'foo',
823
 
            'application-id': 'foo',
824
 
            'application-password': 'foo',
825
 
            'auth-url': 'foo',
826
 
            'authorized-keys': 'foo',
827
 
            'availability-sets-enabled': 'foo',
828
 
            'bootstrap-host': 'foo',
829
 
            'bootstrap-timeout': 'foo',
830
 
            'bootstrap-user': 'foo',
831
 
            'client-email': 'foo',
832
 
            'client-id': 'foo',
833
 
            'container': 'foo',
834
 
            'control-bucket': 'foo',
835
 
            'default-series': 'foo',
836
 
            'development': False,
837
 
            'enable-os-upgrade': 'foo',
838
 
            'image-metadata-url': 'foo',
839
 
            'location': 'foo',
840
 
            'maas-oauth': 'foo',
841
 
            'maas-server': 'foo',
842
 
            'manta-key-id': 'foo',
843
 
            'manta-user': 'foo',
844
 
            'management-subscription-id': 'foo',
845
 
            'management-certificate': 'foo',
846
 
            'name': 'foo',
847
 
            'password': 'foo',
848
 
            'prefer-ipv6': 'foo',
849
 
            'private-key': 'foo',
850
 
            'region': 'foo',
851
 
            'sdc-key-id': 'foo',
852
 
            'sdc-url': 'foo',
853
 
            'sdc-user': 'foo',
854
 
            'secret-key': 'foo',
855
 
            'storage-account-name': 'foo',
856
 
            'subscription-id': 'foo',
857
 
            'tenant-id': 'foo',
858
 
            'tenant-name': 'foo',
859
 
            'test-mode': False,
860
 
            'tools-metadata-url': 'steve',
861
 
            'type': 'foo',
862
 
            'username': 'foo',
863
 
            }, 'home')
864
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
865
 
        with client._bootstrap_config() as config_filename:
866
 
            with open(config_filename) as f:
867
 
                self.assertEqual({
868
 
                    'agent-metadata-url': 'steve',
869
 
                    'agent-stream': 'foo',
870
 
                    'authorized-keys': 'foo',
871
 
                    'availability-sets-enabled': 'foo',
872
 
                    'bootstrap-timeout': 'foo',
873
 
                    'bootstrap-user': 'foo',
874
 
                    'container': 'foo',
875
 
                    'default-series': 'foo',
876
 
                    'development': False,
877
 
                    'enable-os-upgrade': 'foo',
878
 
                    'image-metadata-url': 'foo',
879
 
                    'prefer-ipv6': 'foo',
880
 
                    'test-mode': True,
881
 
                    }, yaml.safe_load(f))
882
 
 
883
 
    def test_get_cloud_region(self):
884
 
        self.assertEqual(
885
 
            'foo/bar', EnvJujuClient.get_cloud_region('foo', 'bar'))
886
 
        self.assertEqual(
887
 
            'foo', EnvJujuClient.get_cloud_region('foo', None))
888
 
 
889
 
    def test_bootstrap_maas(self):
890
 
        env = JujuData('maas', {'type': 'foo', 'region': 'asdf'})
891
 
        with patch.object(EnvJujuClient, 'juju') as mock:
892
 
            client = EnvJujuClient(env, '2.0-zeta1', None)
893
 
            with patch.object(client.env, 'maas', lambda: True):
894
 
                with observable_temp_file() as config_file:
895
 
                    client.bootstrap()
896
 
            mock.assert_called_with(
897
 
                'bootstrap', (
898
 
                    '--constraints', 'mem=2G', 'maas', 'foo/asdf',
899
 
                    '--config', config_file.name, '--default-model', 'maas',
900
 
                    '--agent-version', '2.0'),
901
 
                include_e=False)
902
 
 
903
 
    def test_bootstrap_joyent(self):
904
 
        env = JujuData('joyent', {
905
 
            'type': 'joyent', 'sdc-url': 'https://foo.api.joyentcloud.com'})
906
 
        with patch.object(EnvJujuClient, 'juju', autospec=True) as mock:
907
 
            client = EnvJujuClient(env, '2.0-zeta1', None)
908
 
            with patch.object(client.env, 'joyent', lambda: True):
909
 
                with observable_temp_file() as config_file:
910
 
                    client.bootstrap()
911
 
            mock.assert_called_once_with(
912
 
                client, 'bootstrap', (
913
 
                    '--constraints', 'mem=2G cpu-cores=1', 'joyent',
914
 
                    'joyent/foo', '--config', config_file.name,
915
 
                    '--default-model', 'joyent', '--agent-version', '2.0',
916
 
                    ), include_e=False)
917
 
 
918
 
    def test_bootstrap(self):
919
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
920
 
        with observable_temp_file() as config_file:
921
 
            with patch.object(EnvJujuClient, 'juju') as mock:
922
 
                client = EnvJujuClient(env, '2.0-zeta1', None)
923
 
                client.bootstrap()
924
 
                mock.assert_called_with(
925
 
                    'bootstrap', ('--constraints', 'mem=2G',
926
 
                                  'foo', 'bar/baz',
927
 
                                  '--config', config_file.name,
928
 
                                  '--default-model', 'foo',
929
 
                                  '--agent-version', '2.0'), include_e=False)
930
 
                config_file.seek(0)
931
 
                config = yaml.safe_load(config_file)
932
 
        self.assertEqual({'test-mode': True}, config)
933
 
 
934
 
    def test_bootstrap_upload_tools(self):
935
 
        env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
936
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
937
 
        with patch.object(client.env, 'needs_sudo', lambda: True):
938
 
            with observable_temp_file() as config_file:
939
 
                with patch.object(client, 'juju') as mock:
940
 
                    client.bootstrap(upload_tools=True)
941
 
            mock.assert_called_with(
942
 
                'bootstrap', (
943
 
                    '--upload-tools', '--constraints', 'mem=2G', 'foo',
944
 
                    'foo/baz', '--config', config_file.name,
945
 
                    '--default-model', 'foo'), include_e=False)
946
 
 
947
 
    def test_bootstrap_credential(self):
948
 
        env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
949
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
950
 
        with observable_temp_file() as config_file:
951
 
            with patch.object(client, 'juju') as mock:
952
 
                client.bootstrap(credential='credential_name')
953
 
        mock.assert_called_with(
954
 
            'bootstrap', (
955
 
                '--constraints', 'mem=2G', 'foo',
956
 
                'foo/baz', '--config', config_file.name,
957
 
                '--default-model', 'foo', '--agent-version', '2.0',
958
 
                '--credential', 'credential_name'), include_e=False)
959
 
 
960
 
    def test_bootstrap_bootstrap_series(self):
961
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
962
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
963
 
        with patch.object(client, 'juju') as mock:
964
 
            with observable_temp_file() as config_file:
965
 
                client.bootstrap(bootstrap_series='angsty')
966
 
        mock.assert_called_with(
967
 
            'bootstrap', (
968
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
969
 
                '--config', config_file.name, '--default-model', 'foo',
970
 
                '--agent-version', '2.0',
971
 
                '--bootstrap-series', 'angsty'), include_e=False)
972
 
 
973
 
    def test_bootstrap_auto_upgade(self):
974
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
975
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
976
 
        with patch.object(client, 'juju') as mock:
977
 
            with observable_temp_file() as config_file:
978
 
                client.bootstrap(auto_upgrade=True)
979
 
        mock.assert_called_with(
980
 
            'bootstrap', (
981
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
982
 
                '--config', config_file.name, '--default-model', 'foo',
983
 
                '--agent-version', '2.0', '--auto-upgrade'), include_e=False)
984
 
 
985
 
    def test_bootstrap_metadata(self):
986
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
987
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
988
 
        with patch.object(client, 'juju') as mock:
989
 
            with observable_temp_file() as config_file:
990
 
                client.bootstrap(metadata_source='/var/test-source')
991
 
        mock.assert_called_with(
992
 
            'bootstrap', (
993
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
994
 
                '--config', config_file.name, '--default-model', 'foo',
995
 
                '--agent-version', '2.0',
996
 
                '--metadata-source', '/var/test-source'), include_e=False)
997
 
 
998
 
    def test_bootstrap_to(self):
999
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1000
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1001
 
        with patch.object(client, 'juju') as mock:
1002
 
            with observable_temp_file() as config_file:
1003
 
                client.bootstrap(to='target')
1004
 
        mock.assert_called_with(
1005
 
            'bootstrap', (
1006
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
1007
 
                '--config', config_file.name, '--default-model', 'foo',
1008
 
                '--agent-version', '2.0', '--to', 'target'), include_e=False)
1009
 
 
1010
 
    def test_bootstrap_async(self):
1011
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1012
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
1013
 
            client = EnvJujuClient(env, '2.0-zeta1', None)
1014
 
            client.env.juju_home = 'foo'
1015
 
            with observable_temp_file() as config_file:
1016
 
                with client.bootstrap_async():
1017
 
                    mock.assert_called_once_with(
1018
 
                        client, 'bootstrap', (
1019
 
                            '--constraints', 'mem=2G', 'foo', 'bar/baz',
1020
 
                            '--config', config_file.name,
1021
 
                            '--default-model', 'foo',
1022
 
                            '--agent-version', '2.0'), include_e=False)
1023
 
 
1024
 
    def test_bootstrap_async_upload_tools(self):
1025
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1026
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
1027
 
            client = EnvJujuClient(env, '2.0-zeta1', None)
1028
 
            with observable_temp_file() as config_file:
1029
 
                with client.bootstrap_async(upload_tools=True):
1030
 
                    mock.assert_called_with(
1031
 
                        client, 'bootstrap', (
1032
 
                            '--upload-tools', '--constraints', 'mem=2G',
1033
 
                            'foo', 'bar/baz', '--config', config_file.name,
1034
 
                            '--default-model', 'foo',
1035
 
                            ),
1036
 
                        include_e=False)
1037
 
 
1038
 
    def test_get_bootstrap_args_bootstrap_series(self):
1039
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1040
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1041
 
        args = client.get_bootstrap_args(upload_tools=True,
1042
 
                                         config_filename='config',
1043
 
                                         bootstrap_series='angsty')
1044
 
        self.assertEqual(args, (
1045
 
            '--upload-tools', '--constraints', 'mem=2G', 'foo', 'bar/baz',
1046
 
            '--config', 'config', '--default-model', 'foo',
1047
 
            '--bootstrap-series', 'angsty'))
1048
 
 
1049
 
    def test_get_bootstrap_args_agent_version(self):
1050
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1051
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1052
 
        args = client.get_bootstrap_args(upload_tools=False,
1053
 
                                         config_filename='config',
1054
 
                                         agent_version='2.0-lambda1')
1055
 
        self.assertEqual(('--constraints', 'mem=2G', 'foo', 'bar/baz',
1056
 
                          '--config', 'config', '--default-model', 'foo',
1057
 
                          '--agent-version', '2.0-lambda1'), args)
1058
 
 
1059
 
    def test_get_bootstrap_args_upload_tools_and_agent_version(self):
1060
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1061
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1062
 
        with self.assertRaises(ValueError):
1063
 
            client.get_bootstrap_args(upload_tools=True,
1064
 
                                      config_filename='config',
1065
 
                                      agent_version='2.0-lambda1')
1066
 
 
1067
 
    def test_add_model_hypenated_controller(self):
1068
 
        self.do_add_model(
1069
 
            'kill-controller', 'add-model', ('-c', 'foo'))
1070
 
 
1071
 
    def do_add_model(self, jes_command, create_cmd, controller_option):
1072
 
        controller_client = EnvJujuClient(JujuData('foo'), None, None)
1073
 
        model_data = JujuData('bar', {'type': 'foo'})
1074
 
        client = EnvJujuClient(model_data, None, None)
1075
 
        with patch.object(client, 'get_jes_command',
1076
 
                          return_value=jes_command):
1077
 
                with patch.object(controller_client, 'juju') as ccj_mock:
1078
 
                    with observable_temp_file() as config_file:
1079
 
                        controller_client.add_model(model_data)
1080
 
        ccj_mock.assert_called_once_with(
1081
 
            create_cmd, controller_option + (
1082
 
                'bar', '--config', config_file.name), include_e=False)
1083
 
 
1084
 
    def test_destroy_environment(self):
1085
 
        env = JujuData('foo')
1086
 
        client = EnvJujuClient(env, None, None)
1087
 
        self.assertIs(False, hasattr(client, 'destroy_environment'))
1088
 
 
1089
 
    def test_destroy_model(self):
1090
 
        env = JujuData('foo', {'type': 'gce'})
1091
 
        client = EnvJujuClient(env, None, None)
1092
 
        with patch.object(client, 'juju') as mock:
1093
 
            client.destroy_model()
1094
 
        mock.assert_called_with(
1095
 
            'destroy-model', ('foo', '-y'),
1096
 
            include_e=False, timeout=600)
1097
 
 
1098
 
    def test_destroy_model_azure(self):
1099
 
        env = JujuData('foo', {'type': 'azure'})
1100
 
        client = EnvJujuClient(env, None, None)
1101
 
        with patch.object(client, 'juju') as mock:
1102
 
            client.destroy_model()
1103
 
        mock.assert_called_with(
1104
 
            'destroy-model', ('foo', '-y'),
1105
 
            include_e=False, timeout=1800)
1106
 
 
1107
 
    def test_kill_controller_system(self):
1108
 
        self.do_kill_controller('system', 'system kill')
1109
 
 
1110
 
    def test_kill_controller_controller(self):
1111
 
        self.do_kill_controller('controller', 'controller kill')
1112
 
 
1113
 
    def test_kill_controller_hyphenated(self):
1114
 
        self.do_kill_controller('kill-controller', 'kill-controller')
1115
 
 
1116
 
    def do_kill_controller(self, jes_command, kill_command):
1117
 
        client = EnvJujuClient(JujuData('foo', {'type': 'gce'}), None, None)
1118
 
        with patch.object(client, 'get_jes_command',
1119
 
                          return_value=jes_command):
1120
 
            with patch.object(client, 'juju') as juju_mock:
1121
 
                client.kill_controller()
1122
 
        juju_mock.assert_called_once_with(
1123
 
            kill_command, ('foo', '-y'), check=False, include_e=False,
1124
 
            timeout=600)
1125
 
 
1126
 
    def do_kill_controller_azure(self, jes_command, kill_command):
1127
 
        client = EnvJujuClient(JujuData('foo', {'type': 'azure'}), None, None)
1128
 
        with patch.object(client, 'get_jes_command',
1129
 
                          return_value=jes_command):
1130
 
            with patch.object(client, 'juju') as juju_mock:
1131
 
                client.kill_controller()
1132
 
        juju_mock.assert_called_once_with(
1133
 
            kill_command, ('foo', '-y'), check=False, include_e=False,
1134
 
            timeout=1800)
1135
 
 
1136
 
    def test_get_juju_output(self):
1137
 
        env = JujuData('foo')
1138
 
        client = EnvJujuClient(env, None, 'juju')
1139
 
        fake_popen = FakePopen('asdf', None, 0)
1140
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
1141
 
            result = client.get_juju_output('bar')
1142
 
        self.assertEqual('asdf', result)
1143
 
        self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo'),),
1144
 
                         mock.call_args[0])
1145
 
 
1146
 
    def test_get_juju_output_accepts_varargs(self):
1147
 
        env = JujuData('foo')
1148
 
        fake_popen = FakePopen('asdf', None, 0)
1149
 
        client = EnvJujuClient(env, None, 'juju')
1150
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
1151
 
            result = client.get_juju_output('bar', 'baz', '--qux')
1152
 
        self.assertEqual('asdf', result)
1153
 
        self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo', 'baz',
1154
 
                           '--qux'),), mock.call_args[0])
1155
 
 
1156
 
    def test_get_juju_output_stderr(self):
1157
 
        env = JujuData('foo')
1158
 
        fake_popen = FakePopen(None, 'Hello!', 1)
1159
 
        client = EnvJujuClient(env, None, 'juju')
1160
 
        with self.assertRaises(subprocess.CalledProcessError) as exc:
1161
 
            with patch('subprocess.Popen', return_value=fake_popen):
1162
 
                client.get_juju_output('bar')
1163
 
        self.assertEqual(exc.exception.stderr, 'Hello!')
1164
 
 
1165
 
    def test_get_juju_output_merge_stderr(self):
1166
 
        env = JujuData('foo')
1167
 
        fake_popen = FakePopen('Err on out', None, 0)
1168
 
        client = EnvJujuClient(env, None, 'juju')
1169
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock_popen:
1170
 
            result = client.get_juju_output('bar', merge_stderr=True)
1171
 
        self.assertEqual(result, 'Err on out')
1172
 
        mock_popen.assert_called_once_with(
1173
 
            ('juju', '--show-log', 'bar', '-m', 'foo:foo'),
1174
 
            stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
1175
 
            stdout=subprocess.PIPE)
1176
 
 
1177
 
    def test_get_juju_output_full_cmd(self):
1178
 
        env = JujuData('foo')
1179
 
        fake_popen = FakePopen(None, 'Hello!', 1)
1180
 
        client = EnvJujuClient(env, None, 'juju')
1181
 
        with self.assertRaises(subprocess.CalledProcessError) as exc:
1182
 
            with patch('subprocess.Popen', return_value=fake_popen):
1183
 
                client.get_juju_output('bar', '--baz', 'qux')
1184
 
        self.assertEqual(
1185
 
            ('juju', '--show-log', 'bar', '-m', 'foo:foo', '--baz', 'qux'),
1186
 
            exc.exception.cmd)
1187
 
 
1188
 
    def test_get_juju_output_accepts_timeout(self):
1189
 
        env = JujuData('foo')
1190
 
        fake_popen = FakePopen('asdf', None, 0)
1191
 
        client = EnvJujuClient(env, None, 'juju')
1192
 
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
1193
 
            client.get_juju_output('bar', timeout=5)
1194
 
        self.assertEqual(
1195
 
            po_mock.call_args[0][0],
1196
 
            (sys.executable, get_timeout_path(), '5.00', '--', 'juju',
1197
 
             '--show-log', 'bar', '-m', 'foo:foo'))
1198
 
 
1199
 
    def test__shell_environ_juju_data(self):
1200
 
        client = EnvJujuClient(
1201
 
            JujuData('baz', {'type': 'ec2'}), '1.25-foobar', 'path', 'asdf')
1202
 
        env = client._shell_environ()
1203
 
        self.assertEqual(env['JUJU_DATA'], 'asdf')
1204
 
        self.assertNotIn('JUJU_HOME', env)
1205
 
 
1206
 
    def test__shell_environ_cloudsigma(self):
1207
 
        client = EnvJujuClient(
1208
 
            JujuData('baz', {'type': 'cloudsigma'}), '1.24-foobar', 'path')
1209
 
        env = client._shell_environ()
1210
 
        self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS, ''), '')
1211
 
 
1212
 
    def test_juju_output_supplies_path(self):
1213
 
        env = JujuData('foo')
1214
 
        client = EnvJujuClient(env, None, '/foobar/bar')
1215
 
 
1216
 
        def check_path(*args, **kwargs):
1217
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
1218
 
            return FakePopen(None, None, 0)
1219
 
        with patch('subprocess.Popen', autospec=True,
1220
 
                   side_effect=check_path):
1221
 
            client.get_juju_output('cmd', 'baz')
1222
 
 
1223
 
    def test_get_status(self):
1224
 
        output_text = dedent("""\
1225
 
                - a
1226
 
                - b
1227
 
                - c
1228
 
                """)
1229
 
        env = JujuData('foo')
1230
 
        client = EnvJujuClient(env, None, None)
1231
 
        with patch.object(client, 'get_juju_output',
1232
 
                          return_value=output_text) as gjo_mock:
1233
 
            result = client.get_status()
1234
 
        gjo_mock.assert_called_once_with(
1235
 
            'show-status', '--format', 'yaml', controller=False)
1236
 
        self.assertEqual(Status, type(result))
1237
 
        self.assertEqual(['a', 'b', 'c'], result.status)
1238
 
 
1239
 
    def test_get_status_retries_on_error(self):
1240
 
        env = JujuData('foo')
1241
 
        client = EnvJujuClient(env, None, None)
1242
 
        client.attempt = 0
1243
 
 
1244
 
        def get_juju_output(command, *args, **kwargs):
1245
 
            if client.attempt == 1:
1246
 
                return '"hello"'
1247
 
            client.attempt += 1
1248
 
            raise subprocess.CalledProcessError(1, command)
1249
 
 
1250
 
        with patch.object(client, 'get_juju_output', get_juju_output):
1251
 
            client.get_status()
1252
 
 
1253
 
    def test_get_status_raises_on_timeout_1(self):
1254
 
        env = JujuData('foo')
1255
 
        client = EnvJujuClient(env, None, None)
1256
 
 
1257
 
        def get_juju_output(command, *args, **kwargs):
1258
 
            raise subprocess.CalledProcessError(1, command)
1259
 
 
1260
 
        with patch.object(client, 'get_juju_output',
1261
 
                          side_effect=get_juju_output):
1262
 
            with patch('jujupy.until_timeout', lambda x: iter([None, None])):
1263
 
                with self.assertRaisesRegexp(
1264
 
                        Exception, 'Timed out waiting for juju status'):
1265
 
                    client.get_status()
1266
 
 
1267
 
    def test_get_status_raises_on_timeout_2(self):
1268
 
        env = JujuData('foo')
1269
 
        client = EnvJujuClient(env, None, None)
1270
 
        with patch('jujupy.until_timeout', return_value=iter([1])) as mock_ut:
1271
 
            with patch.object(client, 'get_juju_output',
1272
 
                              side_effect=StopIteration):
1273
 
                with self.assertRaises(StopIteration):
1274
 
                    client.get_status(500)
1275
 
        mock_ut.assert_called_with(500)
1276
 
 
1277
 
    @staticmethod
1278
 
    def make_status_yaml(key, machine_value, unit_value):
1279
 
        return dedent("""\
1280
 
            machines:
1281
 
              "0":
1282
 
                {0}: {1}
1283
 
            applications:
1284
 
              jenkins:
1285
 
                units:
1286
 
                  jenkins/0:
1287
 
                    {0}: {2}
1288
 
        """.format(key, machine_value, unit_value))
1289
 
 
1290
 
    def test_deploy_non_joyent(self):
1291
 
        env = EnvJujuClient(
1292
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1293
 
        with patch.object(env, 'juju') as mock_juju:
1294
 
            env.deploy('mondogb')
1295
 
        mock_juju.assert_called_with('deploy', ('mondogb',))
1296
 
 
1297
 
    def test_deploy_joyent(self):
1298
 
        env = EnvJujuClient(
1299
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1300
 
        with patch.object(env, 'juju') as mock_juju:
1301
 
            env.deploy('mondogb')
1302
 
        mock_juju.assert_called_with('deploy', ('mondogb',))
1303
 
 
1304
 
    def test_deploy_repository(self):
1305
 
        env = EnvJujuClient(
1306
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1307
 
        with patch.object(env, 'juju') as mock_juju:
1308
 
            env.deploy('/home/jrandom/repo/mongodb')
1309
 
        mock_juju.assert_called_with(
1310
 
            'deploy', ('/home/jrandom/repo/mongodb',))
1311
 
 
1312
 
    def test_deploy_to(self):
1313
 
        env = EnvJujuClient(
1314
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1315
 
        with patch.object(env, 'juju') as mock_juju:
1316
 
            env.deploy('mondogb', to='0')
1317
 
        mock_juju.assert_called_with(
1318
 
            'deploy', ('mondogb', '--to', '0'))
1319
 
 
1320
 
    def test_deploy_service(self):
1321
 
        env = EnvJujuClient(
1322
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1323
 
        with patch.object(env, 'juju') as mock_juju:
1324
 
            env.deploy('local:mondogb', service='my-mondogb')
1325
 
        mock_juju.assert_called_with(
1326
 
            'deploy', ('local:mondogb', 'my-mondogb',))
1327
 
 
1328
 
    def test_deploy_force(self):
1329
 
        env = EnvJujuClient(
1330
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1331
 
        with patch.object(env, 'juju') as mock_juju:
1332
 
            env.deploy('local:mondogb', force=True)
1333
 
        mock_juju.assert_called_with('deploy', ('local:mondogb', '--force',))
1334
 
 
1335
 
    def test_deploy_series(self):
1336
 
        env = EnvJujuClient(
1337
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1338
 
        with patch.object(env, 'juju') as mock_juju:
1339
 
            env.deploy('local:blah', series='xenial')
1340
 
        mock_juju.assert_called_with(
1341
 
            'deploy', ('local:blah', '--series', 'xenial'))
1342
 
 
1343
 
    def test_deploy_resource(self):
1344
 
        env = EnvJujuClient(JujuData('foo', {'type': 'local'}), None, None)
1345
 
        with patch.object(env, 'juju') as mock_juju:
1346
 
            env.deploy('local:blah', resource='foo=/path/dir')
1347
 
        mock_juju.assert_called_with(
1348
 
            'deploy', ('local:blah', '--resource', 'foo=/path/dir'))
1349
 
 
1350
 
    def test_deploy_storage(self):
1351
 
        env = EnvJujuClient1X(
1352
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1353
 
        with patch.object(env, 'juju') as mock_juju:
1354
 
            env.deploy('mondogb', storage='rootfs,1G')
1355
 
        mock_juju.assert_called_with(
1356
 
            'deploy', ('mondogb', '--storage', 'rootfs,1G'))
1357
 
 
1358
 
    def test_deploy_constraints(self):
1359
 
        env = EnvJujuClient1X(
1360
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1361
 
        with patch.object(env, 'juju') as mock_juju:
1362
 
            env.deploy('mondogb', constraints='virt-type=kvm')
1363
 
        mock_juju.assert_called_with(
1364
 
            'deploy', ('mondogb', '--constraints', 'virt-type=kvm'))
1365
 
 
1366
 
    def test_attach(self):
1367
 
        env = EnvJujuClient(JujuData('foo', {'type': 'local'}), None, None)
1368
 
        with patch.object(env, 'juju') as mock_juju:
1369
 
            env.attach('foo', resource='foo=/path/dir')
1370
 
        mock_juju.assert_called_with('attach', ('foo', 'foo=/path/dir'))
1371
 
 
1372
 
    def test_list_resources(self):
1373
 
        data = 'resourceid: resource/foo'
1374
 
        client = EnvJujuClient(JujuData('local'), None, None)
1375
 
        with patch.object(
1376
 
                client, 'get_juju_output', return_value=data) as mock_gjo:
1377
 
            status = client.list_resources('foo')
1378
 
        self.assertEqual(status, yaml.safe_load(data))
1379
 
        mock_gjo.assert_called_with(
1380
 
            'list-resources', '--format', 'yaml', 'foo', '--details')
1381
 
 
1382
 
    def test_wait_for_resource(self):
1383
 
        client = EnvJujuClient(JujuData('local'), None, None)
1384
 
        with patch.object(
1385
 
                client, 'list_resources',
1386
 
                return_value=make_resource_list()) as mock_lr:
1387
 
            client.wait_for_resource('dummy-resource/foo', 'foo')
1388
 
        mock_lr.assert_called_once_with('foo')
1389
 
 
1390
 
    def test_wait_for_resource_timeout(self):
1391
 
        client = EnvJujuClient(JujuData('local'), None, None)
1392
 
        resource_list = make_resource_list()
1393
 
        resource_list['resources'][0]['expected']['resourceid'] = 'bad_id'
1394
 
        with patch.object(
1395
 
                client, 'list_resources',
1396
 
                return_value=resource_list) as mock_lr:
1397
 
            with patch('jujupy.until_timeout', autospec=True,
1398
 
                       return_value=[0, 1]) as mock_ju:
1399
 
                with patch('time.sleep', autospec=True) as mock_ts:
1400
 
                    with self.assertRaisesRegexp(
1401
 
                            JujuResourceTimeout,
1402
 
                            'Timeout waiting for a resource to be downloaded'):
1403
 
                        client.wait_for_resource('dummy-resource/foo', 'foo')
1404
 
        calls = [call('foo'), call('foo')]
1405
 
        self.assertEqual(mock_lr.mock_calls, calls)
1406
 
        self.assertEqual(mock_ts.mock_calls, [call(.1), call(.1)])
1407
 
        self.assertEqual(mock_ju.mock_calls, [call(60)])
1408
 
 
1409
 
    def test_wait_for_resource_suppresses_deadline(self):
1410
 
        with client_past_deadline() as client:
1411
 
            real_check_timeouts = client.check_timeouts
1412
 
 
1413
 
            def list_resources(service_or_unit):
1414
 
                with real_check_timeouts():
1415
 
                    return make_resource_list()
1416
 
 
1417
 
            with patch.object(client, 'check_timeouts', autospec=True):
1418
 
                with patch.object(client, 'list_resources', autospec=True,
1419
 
                                  side_effect=list_resources):
1420
 
                        client.wait_for_resource('dummy-resource/foo',
1421
 
                                                 'app_unit')
1422
 
 
1423
 
    def test_wait_for_resource_checks_deadline(self):
1424
 
        resource_list = make_resource_list()
1425
 
        with client_past_deadline() as client:
1426
 
            with patch.object(client, 'list_resources', autospec=True,
1427
 
                              return_value=resource_list):
1428
 
                with self.assertRaises(SoftDeadlineExceeded):
1429
 
                    client.wait_for_resource('dummy-resource/foo', 'app_unit')
1430
 
 
1431
 
    def test_deploy_bundle_2x(self):
1432
 
        client = EnvJujuClient(JujuData('an_env', None),
1433
 
                               '1.23-series-arch', None)
1434
 
        with patch.object(client, 'juju') as mock_juju:
1435
 
            client.deploy_bundle('bundle:~juju-qa/some-bundle')
1436
 
        mock_juju.assert_called_with(
1437
 
            'deploy', ('bundle:~juju-qa/some-bundle'), timeout=3600)
1438
 
 
1439
 
    def test_deploy_bundle_template(self):
1440
 
        client = EnvJujuClient(JujuData('an_env', None),
1441
 
                               '1.23-series-arch', None)
1442
 
        with patch.object(client, 'juju') as mock_juju:
1443
 
            client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
1444
 
        mock_juju.assert_called_with(
1445
 
            'deploy', ('bundle:~juju-qa/some-lxd-bundle'), timeout=3600)
1446
 
 
1447
 
    def test_upgrade_charm(self):
1448
 
        env = EnvJujuClient(
1449
 
            JujuData('foo', {'type': 'local'}), '2.34-74', None)
1450
 
        with patch.object(env, 'juju') as mock_juju:
1451
 
            env.upgrade_charm('foo-service',
1452
 
                              '/bar/repository/angsty/mongodb')
1453
 
        mock_juju.assert_called_once_with(
1454
 
            'upgrade-charm', ('foo-service', '--path',
1455
 
                              '/bar/repository/angsty/mongodb',))
1456
 
 
1457
 
    def test_remove_service(self):
1458
 
        env = EnvJujuClient(
1459
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1460
 
        with patch.object(env, 'juju') as mock_juju:
1461
 
            env.remove_service('mondogb')
1462
 
        mock_juju.assert_called_with('remove-application', ('mondogb',))
1463
 
 
1464
 
    def test_status_until_always_runs_once(self):
1465
 
        client = EnvJujuClient(
1466
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1467
 
        status_txt = self.make_status_yaml('agent-state', 'started', 'started')
1468
 
        with patch.object(client, 'get_juju_output', return_value=status_txt):
1469
 
            result = list(client.status_until(-1))
1470
 
        self.assertEqual(
1471
 
            [r.status for r in result], [Status.from_text(status_txt).status])
1472
 
 
1473
 
    def test_status_until_timeout(self):
1474
 
        client = EnvJujuClient(
1475
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1476
 
        status_txt = self.make_status_yaml('agent-state', 'started', 'started')
1477
 
        status_yaml = yaml.safe_load(status_txt)
1478
 
 
1479
 
        def until_timeout_stub(timeout, start=None):
1480
 
            return iter([None, None])
1481
 
 
1482
 
        with patch.object(client, 'get_juju_output', return_value=status_txt):
1483
 
            with patch('jujupy.until_timeout',
1484
 
                       side_effect=until_timeout_stub) as ut_mock:
1485
 
                result = list(client.status_until(30, 70))
1486
 
        self.assertEqual(
1487
 
            [r.status for r in result], [status_yaml] * 3)
1488
 
        # until_timeout is called by status as well as status_until.
1489
 
        self.assertEqual(ut_mock.mock_calls,
1490
 
                         [call(60), call(30, start=70), call(60), call(60)])
1491
 
 
1492
 
    def test_status_until_suppresses_deadline(self):
1493
 
        with self.only_status_checks() as client:
1494
 
            list(client.status_until(0))
1495
 
 
1496
 
    def test_status_until_checks_deadline(self):
1497
 
        with self.status_does_not_check() as client:
1498
 
            with self.assertRaises(SoftDeadlineExceeded):
1499
 
                list(client.status_until(0))
1500
 
 
1501
 
    def test_add_ssh_machines(self):
1502
 
        client = EnvJujuClient(JujuData('foo'), None, 'juju')
1503
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
1504
 
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1505
 
        assert_juju_call(
1506
 
            self,
1507
 
            cc_mock,
1508
 
            client,
1509
 
            ('juju', '--show-log', 'add-machine',
1510
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1511
 
            0)
1512
 
        assert_juju_call(
1513
 
            self,
1514
 
            cc_mock,
1515
 
            client,
1516
 
            ('juju', '--show-log', 'add-machine',
1517
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1518
 
            1)
1519
 
        assert_juju_call(
1520
 
            self,
1521
 
            cc_mock,
1522
 
            client,
1523
 
            ('juju', '--show-log', 'add-machine',
1524
 
             '-m', 'foo:foo', 'ssh:m-baz'),
1525
 
            2)
1526
 
        self.assertEqual(cc_mock.call_count, 3)
1527
 
 
1528
 
    def test_add_ssh_machines_retry(self):
1529
 
        client = EnvJujuClient(JujuData('foo'), None, 'juju')
1530
 
        with patch('subprocess.check_call', autospec=True,
1531
 
                   side_effect=[subprocess.CalledProcessError(None, None),
1532
 
                                None, None, None]) as cc_mock:
1533
 
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1534
 
        assert_juju_call(
1535
 
            self,
1536
 
            cc_mock,
1537
 
            client,
1538
 
            ('juju', '--show-log', 'add-machine',
1539
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1540
 
            0)
1541
 
        self.pause_mock.assert_called_once_with(30)
1542
 
        assert_juju_call(
1543
 
            self,
1544
 
            cc_mock,
1545
 
            client,
1546
 
            ('juju', '--show-log', 'add-machine',
1547
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1548
 
            1)
1549
 
        assert_juju_call(
1550
 
            self,
1551
 
            cc_mock,
1552
 
            client,
1553
 
            ('juju', '--show-log', 'add-machine',
1554
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1555
 
            2)
1556
 
        assert_juju_call(
1557
 
            self,
1558
 
            cc_mock,
1559
 
            client,
1560
 
            ('juju', '--show-log', 'add-machine',
1561
 
             '-m', 'foo:foo', 'ssh:m-baz'),
1562
 
            3)
1563
 
        self.assertEqual(cc_mock.call_count, 4)
1564
 
 
1565
 
    def test_add_ssh_machines_fail_on_second_machine(self):
1566
 
        client = EnvJujuClient(JujuData('foo'), None, 'juju')
1567
 
        with patch('subprocess.check_call', autospec=True, side_effect=[
1568
 
                None, subprocess.CalledProcessError(None, None), None, None
1569
 
                ]) as cc_mock:
1570
 
            with self.assertRaises(subprocess.CalledProcessError):
1571
 
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1572
 
        assert_juju_call(
1573
 
            self,
1574
 
            cc_mock,
1575
 
            client,
1576
 
            ('juju', '--show-log', 'add-machine',
1577
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1578
 
            0)
1579
 
        assert_juju_call(
1580
 
            self,
1581
 
            cc_mock,
1582
 
            client,
1583
 
            ('juju', '--show-log', 'add-machine',
1584
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1585
 
            1)
1586
 
        self.assertEqual(cc_mock.call_count, 2)
1587
 
 
1588
 
    def test_add_ssh_machines_fail_on_second_attempt(self):
1589
 
        client = EnvJujuClient(JujuData('foo'), None, 'juju')
1590
 
        with patch('subprocess.check_call', autospec=True, side_effect=[
1591
 
                subprocess.CalledProcessError(None, None),
1592
 
                subprocess.CalledProcessError(None, None)]) as cc_mock:
1593
 
            with self.assertRaises(subprocess.CalledProcessError):
1594
 
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1595
 
        assert_juju_call(
1596
 
            self,
1597
 
            cc_mock,
1598
 
            client,
1599
 
            ('juju', '--show-log', 'add-machine',
1600
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1601
 
            0)
1602
 
        assert_juju_call(
1603
 
            self,
1604
 
            cc_mock,
1605
 
            client,
1606
 
            ('juju', '--show-log', 'add-machine',
1607
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1608
 
            1)
1609
 
        self.assertEqual(cc_mock.call_count, 2)
1610
 
 
1611
 
    def test_wait_for_started(self):
1612
 
        value = self.make_status_yaml('agent-state', 'started', 'started')
1613
 
        client = EnvJujuClient(JujuData('local'), None, None)
1614
 
        with patch.object(client, 'get_juju_output', return_value=value):
1615
 
            client.wait_for_started()
1616
 
 
1617
 
    def test_wait_for_started_timeout(self):
1618
 
        value = self.make_status_yaml('agent-state', 'pending', 'started')
1619
 
        client = EnvJujuClient(JujuData('local'), None, None)
1620
 
        with patch('jujupy.until_timeout', lambda x, start=None: range(1)):
1621
 
            with patch.object(client, 'get_juju_output', return_value=value):
1622
 
                writes = []
1623
 
                with patch.object(GroupReporter, '_write', autospec=True,
1624
 
                                  side_effect=lambda _, s: writes.append(s)):
1625
 
                    with self.assertRaisesRegexp(
1626
 
                            Exception,
1627
 
                            'Timed out waiting for agents to start in local'):
1628
 
                        client.wait_for_started()
1629
 
                self.assertEqual(writes, ['pending: 0', ' .', '\n'])
1630
 
 
1631
 
    def test_wait_for_started_start(self):
1632
 
        value = self.make_status_yaml('agent-state', 'started', 'pending')
1633
 
        client = EnvJujuClient(JujuData('local'), None, None)
1634
 
        now = datetime.now() + timedelta(days=1)
1635
 
        with patch('utility.until_timeout.now', return_value=now):
1636
 
            with patch.object(client, 'get_juju_output', return_value=value):
1637
 
                writes = []
1638
 
                with patch.object(GroupReporter, '_write', autospec=True,
1639
 
                                  side_effect=lambda _, s: writes.append(s)):
1640
 
                    with self.assertRaisesRegexp(
1641
 
                            Exception,
1642
 
                            'Timed out waiting for agents to start in local'):
1643
 
                        client.wait_for_started(start=now - timedelta(1200))
1644
 
                self.assertEqual(writes, ['pending: jenkins/0', '\n'])
1645
 
 
1646
 
    def make_ha_status(self):
1647
 
        return {'machines': {
1648
 
            '0': {'controller-member-status': 'has-vote'},
1649
 
            '1': {'controller-member-status': 'has-vote'},
1650
 
            '2': {'controller-member-status': 'has-vote'},
1651
 
            }}
1652
 
 
1653
 
    @contextmanager
1654
 
    def only_status_checks(self, status=None):
1655
 
        """This context manager ensure only get_status calls check_timeouts.
1656
 
 
1657
 
        Everything else will get a mock object.
1658
 
 
1659
 
        Also, the client is patched so that the soft_deadline has been hit.
1660
 
        """
1661
 
        with client_past_deadline() as client:
1662
 
            # This will work even after we patch check_timeouts below.
1663
 
            real_check_timeouts = client.check_timeouts
1664
 
 
1665
 
            def check(timeout=60, controller=False):
1666
 
                with real_check_timeouts():
1667
 
                    return client.status_class(status, '')
1668
 
 
1669
 
            with patch.object(client, 'get_status', autospec=True,
1670
 
                              side_effect=check):
1671
 
                with patch.object(client, 'check_timeouts', autospec=True):
1672
 
                    yield client
1673
 
 
1674
 
    def test__wait_for_status_suppresses_deadline(self):
1675
 
 
1676
 
        def translate(x):
1677
 
            return None
1678
 
 
1679
 
        with self.only_status_checks() as client:
1680
 
            client._wait_for_status(Mock(), translate)
1681
 
 
1682
 
    @contextmanager
1683
 
    def status_does_not_check(self, status=None):
1684
 
        """This context manager ensure get_status never calls check_timeouts.
1685
 
 
1686
 
        Also, the client is patched so that the soft_deadline has been hit.
1687
 
        """
1688
 
        with client_past_deadline() as client:
1689
 
            status_obj = client.status_class(status, '')
1690
 
            with patch.object(client, 'get_status', autospec=True,
1691
 
                              return_value=status_obj):
1692
 
                yield client
1693
 
 
1694
 
    def test__wait_for_status_checks_deadline(self):
1695
 
 
1696
 
        def translate(x):
1697
 
            return None
1698
 
 
1699
 
        with self.status_does_not_check() as client:
1700
 
            with self.assertRaises(SoftDeadlineExceeded):
1701
 
                client._wait_for_status(Mock(), translate)
1702
 
 
1703
 
    def test_wait_for_started_logs_status(self):
1704
 
        value = self.make_status_yaml('agent-state', 'pending', 'started')
1705
 
        client = EnvJujuClient(JujuData('local'), None, None)
1706
 
        with patch.object(client, 'get_juju_output', return_value=value):
1707
 
            writes = []
1708
 
            with patch.object(GroupReporter, '_write', autospec=True,
1709
 
                              side_effect=lambda _, s: writes.append(s)):
1710
 
                with self.assertRaisesRegexp(
1711
 
                        Exception,
1712
 
                        'Timed out waiting for agents to start in local'):
1713
 
                    client.wait_for_started(0)
1714
 
            self.assertEqual(writes, ['pending: 0', '\n'])
1715
 
        self.assertEqual(self.log_stream.getvalue(), 'ERROR %s\n' % value)
1716
 
 
1717
 
    def test_wait_for_subordinate_units(self):
1718
 
        value = dedent("""\
1719
 
            machines:
1720
 
              "0":
1721
 
                agent-state: started
1722
 
            services:
1723
 
              jenkins:
1724
 
                units:
1725
 
                  jenkins/0:
1726
 
                    subordinates:
1727
 
                      sub1/0:
1728
 
                        agent-state: started
1729
 
              ubuntu:
1730
 
                units:
1731
 
                  ubuntu/0:
1732
 
                    subordinates:
1733
 
                      sub2/0:
1734
 
                        agent-state: started
1735
 
                      sub3/0:
1736
 
                        agent-state: started
1737
 
        """)
1738
 
        client = EnvJujuClient(JujuData('local'), None, None)
1739
 
        now = datetime.now() + timedelta(days=1)
1740
 
        with patch('utility.until_timeout.now', return_value=now):
1741
 
            with patch.object(client, 'get_juju_output', return_value=value):
1742
 
                with patch('jujupy.GroupReporter.update') as update_mock:
1743
 
                    with patch('jujupy.GroupReporter.finish') as finish_mock:
1744
 
                        client.wait_for_subordinate_units(
1745
 
                            'jenkins', 'sub1', start=now - timedelta(1200))
1746
 
        self.assertEqual([], update_mock.call_args_list)
1747
 
        finish_mock.assert_called_once_with()
1748
 
 
1749
 
    def test_wait_for_subordinate_units_with_agent_status(self):
1750
 
        value = dedent("""\
1751
 
            machines:
1752
 
              "0":
1753
 
                agent-state: started
1754
 
            services:
1755
 
              jenkins:
1756
 
                units:
1757
 
                  jenkins/0:
1758
 
                    subordinates:
1759
 
                      sub1/0:
1760
 
                        agent-status:
1761
 
                          current: idle
1762
 
              ubuntu:
1763
 
                units:
1764
 
                  ubuntu/0:
1765
 
                    subordinates:
1766
 
                      sub2/0:
1767
 
                        agent-status:
1768
 
                          current: idle
1769
 
                      sub3/0:
1770
 
                        agent-status:
1771
 
                          current: idle
1772
 
        """)
1773
 
        client = EnvJujuClient(JujuData('local'), None, None)
1774
 
        now = datetime.now() + timedelta(days=1)
1775
 
        with patch('utility.until_timeout.now', return_value=now):
1776
 
            with patch.object(client, 'get_juju_output', return_value=value):
1777
 
                with patch('jujupy.GroupReporter.update') as update_mock:
1778
 
                    with patch('jujupy.GroupReporter.finish') as finish_mock:
1779
 
                        client.wait_for_subordinate_units(
1780
 
                            'jenkins', 'sub1', start=now - timedelta(1200))
1781
 
        self.assertEqual([], update_mock.call_args_list)
1782
 
        finish_mock.assert_called_once_with()
1783
 
 
1784
 
    def test_wait_for_multiple_subordinate_units(self):
1785
 
        value = dedent("""\
1786
 
            machines:
1787
 
              "0":
1788
 
                agent-state: started
1789
 
            services:
1790
 
              ubuntu:
1791
 
                units:
1792
 
                  ubuntu/0:
1793
 
                    subordinates:
1794
 
                      sub/0:
1795
 
                        agent-state: started
1796
 
                  ubuntu/1:
1797
 
                    subordinates:
1798
 
                      sub/1:
1799
 
                        agent-state: started
1800
 
        """)
1801
 
        client = EnvJujuClient(JujuData('local'), None, None)
1802
 
        now = datetime.now() + timedelta(days=1)
1803
 
        with patch('utility.until_timeout.now', return_value=now):
1804
 
            with patch.object(client, 'get_juju_output', return_value=value):
1805
 
                with patch('jujupy.GroupReporter.update') as update_mock:
1806
 
                    with patch('jujupy.GroupReporter.finish') as finish_mock:
1807
 
                        client.wait_for_subordinate_units(
1808
 
                            'ubuntu', 'sub', start=now - timedelta(1200))
1809
 
        self.assertEqual([], update_mock.call_args_list)
1810
 
        finish_mock.assert_called_once_with()
1811
 
 
1812
 
    def test_wait_for_subordinate_units_checks_slash_in_unit_name(self):
1813
 
        value = dedent("""\
1814
 
            machines:
1815
 
              "0":
1816
 
                agent-state: started
1817
 
            applications:
1818
 
              jenkins:
1819
 
                units:
1820
 
                  jenkins/0:
1821
 
                    subordinates:
1822
 
                      sub1:
1823
 
                        agent-state: started
1824
 
        """)
1825
 
        client = EnvJujuClient(JujuData('local'), None, None)
1826
 
        now = datetime.now() + timedelta(days=1)
1827
 
        with patch('utility.until_timeout.now', return_value=now):
1828
 
            with patch.object(client, 'get_juju_output', return_value=value):
1829
 
                with self.assertRaisesRegexp(
1830
 
                        Exception,
1831
 
                        'Timed out waiting for agents to start in local'):
1832
 
                    client.wait_for_subordinate_units(
1833
 
                        'jenkins', 'sub1', start=now - timedelta(1200))
1834
 
 
1835
 
    def test_wait_for_subordinate_units_no_subordinate(self):
1836
 
        value = dedent("""\
1837
 
            machines:
1838
 
              "0":
1839
 
                agent-state: started
1840
 
            applications:
1841
 
              jenkins:
1842
 
                units:
1843
 
                  jenkins/0:
1844
 
                    agent-state: started
1845
 
        """)
1846
 
        client = EnvJujuClient(JujuData('local'), None, None)
1847
 
        now = datetime.now() + timedelta(days=1)
1848
 
        with patch('utility.until_timeout.now', return_value=now):
1849
 
            with patch.object(client, 'get_juju_output', return_value=value):
1850
 
                with self.assertRaisesRegexp(
1851
 
                        Exception,
1852
 
                        'Timed out waiting for agents to start in local'):
1853
 
                    client.wait_for_subordinate_units(
1854
 
                        'jenkins', 'sub1', start=now - timedelta(1200))
1855
 
 
1856
 
    def test_wait_for_workload(self):
1857
 
        initial_status = Status.from_text("""\
1858
 
            applications:
1859
 
              jenkins:
1860
 
                units:
1861
 
                  jenkins/0:
1862
 
                    workload-status:
1863
 
                      current: waiting
1864
 
                  subordinates:
1865
 
                    ntp/0:
1866
 
                      workload-status:
1867
 
                        current: unknown
1868
 
        """)
1869
 
        final_status = Status(copy.deepcopy(initial_status.status), None)
1870
 
        final_status.status['applications']['jenkins']['units']['jenkins/0'][
1871
 
            'workload-status']['current'] = 'active'
1872
 
        client = EnvJujuClient(JujuData('local'), None, None)
1873
 
        writes = []
1874
 
        with patch('utility.until_timeout', autospec=True, return_value=[1]):
1875
 
            with patch.object(client, 'get_status', autospec=True,
1876
 
                              side_effect=[initial_status, final_status]):
1877
 
                with patch.object(GroupReporter, '_write', autospec=True,
1878
 
                                  side_effect=lambda _, s: writes.append(s)):
1879
 
                    client.wait_for_workloads()
1880
 
        self.assertEqual(writes, ['waiting: jenkins/0', '\n'])
1881
 
 
1882
 
    def test_wait_for_workload_all_unknown(self):
1883
 
        status = Status.from_text("""\
1884
 
            services:
1885
 
              jenkins:
1886
 
                units:
1887
 
                  jenkins/0:
1888
 
                    workload-status:
1889
 
                      current: unknown
1890
 
                  subordinates:
1891
 
                    ntp/0:
1892
 
                      workload-status:
1893
 
                        current: unknown
1894
 
        """)
1895
 
        client = EnvJujuClient(JujuData('local'), None, None)
1896
 
        writes = []
1897
 
        with patch('utility.until_timeout', autospec=True, return_value=[]):
1898
 
            with patch.object(client, 'get_status', autospec=True,
1899
 
                              return_value=status):
1900
 
                with patch.object(GroupReporter, '_write', autospec=True,
1901
 
                                  side_effect=lambda _, s: writes.append(s)):
1902
 
                    client.wait_for_workloads(timeout=1)
1903
 
        self.assertEqual(writes, [])
1904
 
 
1905
 
    def test_wait_for_workload_no_workload_status(self):
1906
 
        status = Status.from_text("""\
1907
 
            services:
1908
 
              jenkins:
1909
 
                units:
1910
 
                  jenkins/0:
1911
 
                    agent-state: active
1912
 
        """)
1913
 
        client = EnvJujuClient(JujuData('local'), None, None)
1914
 
        writes = []
1915
 
        with patch('utility.until_timeout', autospec=True, return_value=[]):
1916
 
            with patch.object(client, 'get_status', autospec=True,
1917
 
                              return_value=status):
1918
 
                with patch.object(GroupReporter, '_write', autospec=True,
1919
 
                                  side_effect=lambda _, s: writes.append(s)):
1920
 
                    client.wait_for_workloads(timeout=1)
1921
 
        self.assertEqual(writes, [])
1922
 
 
1923
 
    def test_list_models(self):
1924
 
        client = EnvJujuClient(JujuData('foo'), None, None)
1925
 
        with patch.object(client, 'juju') as j_mock:
1926
 
            client.list_models()
1927
 
        j_mock.assert_called_once_with(
1928
 
            'list-models', ('-c', 'foo'), include_e=False)
1929
 
 
1930
 
    def test_get_models(self):
1931
 
        data = """\
1932
 
            models:
1933
 
            - name: foo
1934
 
              model-uuid: aaaa
1935
 
              owner: admin@local
1936
 
            - name: bar
1937
 
              model-uuid: bbbb
1938
 
              owner: admin@local
1939
 
            current-model: foo
1940
 
        """
1941
 
        client = EnvJujuClient(JujuData('baz'), None, None)
1942
 
        with patch.object(client, 'get_juju_output',
1943
 
                          return_value=data) as gjo_mock:
1944
 
            models = client.get_models()
1945
 
        gjo_mock.assert_called_once_with(
1946
 
            'list-models', '-c', 'baz', '--format', 'yaml',
1947
 
            include_e=False, timeout=120)
1948
 
        expected_models = {
1949
 
            'models': [
1950
 
                {'name': 'foo', 'model-uuid': 'aaaa', 'owner': 'admin@local'},
1951
 
                {'name': 'bar', 'model-uuid': 'bbbb', 'owner': 'admin@local'}],
1952
 
            'current-model': 'foo'
1953
 
        }
1954
 
        self.assertEqual(expected_models, models)
1955
 
 
1956
 
    def test_iter_model_clients(self):
1957
 
        data = """\
1958
 
            models:
1959
 
            - name: foo
1960
 
              model-uuid: aaaa
1961
 
              owner: admin@local
1962
 
            - name: bar
1963
 
              model-uuid: bbbb
1964
 
              owner: admin@local
1965
 
            current-model: foo
1966
 
        """
1967
 
        client = EnvJujuClient(JujuData('foo', {}), None, None)
1968
 
        with patch.object(client, 'get_juju_output', return_value=data):
1969
 
            model_clients = list(client.iter_model_clients())
1970
 
        self.assertEqual(2, len(model_clients))
1971
 
        self.assertIs(client, model_clients[0])
1972
 
        self.assertEqual('bar', model_clients[1].env.environment)
1973
 
 
1974
 
    def test_get_controller_model_name(self):
1975
 
        models = {
1976
 
            'models': [
1977
 
                {'name': 'controller', 'model-uuid': 'aaaa'},
1978
 
                {'name': 'bar', 'model-uuid': 'bbbb'}],
1979
 
            'current-model': 'bar'
1980
 
        }
1981
 
        client = EnvJujuClient(JujuData('foo'), None, None)
1982
 
        with patch.object(client, 'get_models',
1983
 
                          return_value=models) as gm_mock:
1984
 
            controller_name = client.get_controller_model_name()
1985
 
        self.assertEqual(0, gm_mock.call_count)
1986
 
        self.assertEqual('controller', controller_name)
1987
 
 
1988
 
    def test_get_controller_model_name_without_controller(self):
1989
 
        models = {
1990
 
            'models': [
1991
 
                {'name': 'bar', 'model-uuid': 'aaaa'},
1992
 
                {'name': 'baz', 'model-uuid': 'bbbb'}],
1993
 
            'current-model': 'bar'
1994
 
        }
1995
 
        client = EnvJujuClient(JujuData('foo'), None, None)
1996
 
        with patch.object(client, 'get_models', return_value=models):
1997
 
            controller_name = client.get_controller_model_name()
1998
 
        self.assertEqual('controller', controller_name)
1999
 
 
2000
 
    def test_get_controller_model_name_no_models(self):
2001
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2002
 
        with patch.object(client, 'get_models', return_value={}):
2003
 
            controller_name = client.get_controller_model_name()
2004
 
        self.assertEqual('controller', controller_name)
2005
 
 
2006
 
    def test_get_model_uuid_returns_uuid(self):
2007
 
        model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194'
2008
 
        yaml_string = dedent("""\
2009
 
        foo:
2010
 
          name: foo
2011
 
          model-uuid: {uuid}
2012
 
          controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202
2013
 
          owner: admin@local
2014
 
          cloud: lxd
2015
 
          region: localhost
2016
 
          type: lxd
2017
 
          life: alive
2018
 
          status:
2019
 
            current: available
2020
 
            since: 1 minute ago
2021
 
          users:
2022
 
            admin@local:
2023
 
              display-name: admin
2024
 
              access: admin
2025
 
              last-connection: just now
2026
 
            """.format(uuid=model_uuid))
2027
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2028
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2029
 
            m_get_juju_output.return_value = yaml_string
2030
 
            self.assertEqual(
2031
 
                client.get_model_uuid(),
2032
 
                model_uuid
2033
 
            )
2034
 
            m_get_juju_output.assert_called_once_with(
2035
 
                'show-model', '--format', 'yaml', 'foo:foo', include_e=False)
2036
 
 
2037
 
    def test_get_controller_model_uuid_returns_uuid(self):
2038
 
        controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202'
2039
 
        controller_model_uuid = '1c908e10-4f07-459a-8419-bb61553a4660'
2040
 
        yaml_string = dedent("""\
2041
 
        controller:
2042
 
          name: controller
2043
 
          model-uuid: {model}
2044
 
          controller-uuid: {controller}
2045
 
          controller-name: localtempveebers
2046
 
          owner: admin@local
2047
 
          cloud: lxd
2048
 
          region: localhost
2049
 
          type: lxd
2050
 
          life: alive
2051
 
          status:
2052
 
            current: available
2053
 
            since: 59 seconds ago
2054
 
          users:
2055
 
            admin@local:
2056
 
              display-name: admin
2057
 
              access: admin
2058
 
              last-connection: just now""".format(model=controller_model_uuid,
2059
 
                                                  controller=controller_uuid))
2060
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2061
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2062
 
            m_get_juju_output.return_value = yaml_string
2063
 
            self.assertEqual(
2064
 
                client.get_controller_model_uuid(),
2065
 
                controller_model_uuid
2066
 
            )
2067
 
            m_get_juju_output.assert_called_once_with(
2068
 
                'show-model', 'controller',
2069
 
                '--format', 'yaml', include_e=False)
2070
 
 
2071
 
    def test_get_controller_uuid_returns_uuid(self):
2072
 
        controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202'
2073
 
        yaml_string = dedent("""\
2074
 
        foo:
2075
 
          details:
2076
 
            uuid: {uuid}
2077
 
            api-endpoints: ['10.194.140.213:17070']
2078
 
            cloud: lxd
2079
 
            region: localhost
2080
 
          models:
2081
 
            controller:
2082
 
              uuid: {uuid}
2083
 
            default:
2084
 
              uuid: 772cdd39-b454-4bd5-8704-dc9aa9ff1750
2085
 
          current-model: default
2086
 
          account:
2087
 
            user: admin@local
2088
 
          bootstrap-config:
2089
 
            config:
2090
 
            cloud: lxd
2091
 
            cloud-type: lxd
2092
 
            region: localhost""".format(uuid=controller_uuid))
2093
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2094
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2095
 
            m_get_juju_output.return_value = yaml_string
2096
 
            self.assertEqual(
2097
 
                client.get_controller_uuid(),
2098
 
                controller_uuid
2099
 
            )
2100
 
            m_get_juju_output.assert_called_once_with(
2101
 
                'show-controller', '--format', 'yaml', include_e=False)
2102
 
 
2103
 
    def test_get_controller_client(self):
2104
 
        client = EnvJujuClient(
2105
 
            JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)
2106
 
        controller_client = client.get_controller_client()
2107
 
        controller_env = controller_client.env
2108
 
        self.assertEqual('controller', controller_env.environment)
2109
 
        self.assertEqual(
2110
 
            {'bar': 'baz', 'name': 'controller'}, controller_env.config)
2111
 
 
2112
 
    def test_list_controllers(self):
2113
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2114
 
        with patch.object(client, 'juju') as j_mock:
2115
 
            client.list_controllers()
2116
 
        j_mock.assert_called_once_with('list-controllers', (), include_e=False)
2117
 
 
2118
 
    def test_get_controller_endpoint_ipv4(self):
2119
 
        data = """\
2120
 
          foo:
2121
 
            details:
2122
 
              api-endpoints: ['10.0.0.1:17070', '10.0.0.2:17070']
2123
 
        """
2124
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2125
 
        with patch.object(client, 'get_juju_output',
2126
 
                          return_value=data) as gjo_mock:
2127
 
            endpoint = client.get_controller_endpoint()
2128
 
        self.assertEqual('10.0.0.1', endpoint)
2129
 
        gjo_mock.assert_called_once_with(
2130
 
            'show-controller', 'foo', include_e=False)
2131
 
 
2132
 
    def test_get_controller_endpoint_ipv6(self):
2133
 
        data = """\
2134
 
          foo:
2135
 
            details:
2136
 
              api-endpoints: ['[::1]:17070', '[fe80::216:3eff:0:9dc7]:17070']
2137
 
        """
2138
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2139
 
        with patch.object(client, 'get_juju_output',
2140
 
                          return_value=data) as gjo_mock:
2141
 
            endpoint = client.get_controller_endpoint()
2142
 
        self.assertEqual('::1', endpoint)
2143
 
        gjo_mock.assert_called_once_with(
2144
 
            'show-controller', 'foo', include_e=False)
2145
 
 
2146
 
    def test_get_controller_controller_name(self):
2147
 
        data = """\
2148
 
          bar:
2149
 
            details:
2150
 
              api-endpoints: ['[::1]:17070', '[fe80::216:3eff:0:9dc7]:17070']
2151
 
        """
2152
 
        client = EnvJujuClient(JujuData('foo', {}), None, None)
2153
 
        controller_client = client.get_controller_client()
2154
 
        client.env.controller.name = 'bar'
2155
 
        with patch.object(controller_client, 'get_juju_output',
2156
 
                          return_value=data) as gjo:
2157
 
            endpoint = controller_client.get_controller_endpoint()
2158
 
        gjo.assert_called_once_with('show-controller', 'bar',
2159
 
                                    include_e=False)
2160
 
        self.assertEqual('::1', endpoint)
2161
 
 
2162
 
    def test_get_controller_members(self):
2163
 
        status = Status.from_text("""\
2164
 
            model: controller
2165
 
            machines:
2166
 
              "0":
2167
 
                dns-name: 10.0.0.0
2168
 
                instance-id: juju-aaaa-machine-0
2169
 
                controller-member-status: has-vote
2170
 
              "1":
2171
 
                dns-name: 10.0.0.1
2172
 
                instance-id: juju-bbbb-machine-1
2173
 
              "2":
2174
 
                dns-name: 10.0.0.2
2175
 
                instance-id: juju-cccc-machine-2
2176
 
                controller-member-status: has-vote
2177
 
              "3":
2178
 
                dns-name: 10.0.0.3
2179
 
                instance-id: juju-dddd-machine-3
2180
 
                controller-member-status: has-vote
2181
 
        """)
2182
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2183
 
        with patch.object(client, 'get_status', autospec=True,
2184
 
                          return_value=status):
2185
 
            with patch.object(client, 'get_controller_endpoint', autospec=True,
2186
 
                              return_value='10.0.0.3') as gce_mock:
2187
 
                with patch.object(client, 'get_controller_member_status',
2188
 
                                  wraps=client.get_controller_member_status,
2189
 
                                  ) as gcms_mock:
2190
 
                    members = client.get_controller_members()
2191
 
        # Machine 1 was ignored. Machine 3 is the leader, thus first.
2192
 
        expected = [
2193
 
            Machine('3', {
2194
 
                'dns-name': '10.0.0.3',
2195
 
                'instance-id': 'juju-dddd-machine-3',
2196
 
                'controller-member-status': 'has-vote'}),
2197
 
            Machine('0', {
2198
 
                'dns-name': '10.0.0.0',
2199
 
                'instance-id': 'juju-aaaa-machine-0',
2200
 
                'controller-member-status': 'has-vote'}),
2201
 
            Machine('2', {
2202
 
                'dns-name': '10.0.0.2',
2203
 
                'instance-id': 'juju-cccc-machine-2',
2204
 
                'controller-member-status': 'has-vote'}),
2205
 
        ]
2206
 
        self.assertEqual(expected, members)
2207
 
        gce_mock.assert_called_once_with()
2208
 
        # get_controller_member_status must be called to ensure compatibility
2209
 
        # with all version of Juju.
2210
 
        self.assertEqual(4, gcms_mock.call_count)
2211
 
 
2212
 
    def test_get_controller_members_one(self):
2213
 
        status = Status.from_text("""\
2214
 
            model: controller
2215
 
            machines:
2216
 
              "0":
2217
 
                dns-name: 10.0.0.0
2218
 
                instance-id: juju-aaaa-machine-0
2219
 
                controller-member-status: has-vote
2220
 
        """)
2221
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2222
 
        with patch.object(client, 'get_status', autospec=True,
2223
 
                          return_value=status):
2224
 
            with patch.object(client, 'get_controller_endpoint') as gce_mock:
2225
 
                members = client.get_controller_members()
2226
 
        # Machine 0 was the only choice, no need to find the leader.
2227
 
        expected = [
2228
 
            Machine('0', {
2229
 
                'dns-name': '10.0.0.0',
2230
 
                'instance-id': 'juju-aaaa-machine-0',
2231
 
                'controller-member-status': 'has-vote'}),
2232
 
        ]
2233
 
        self.assertEqual(expected, members)
2234
 
        self.assertEqual(0, gce_mock.call_count)
2235
 
 
2236
 
    def test_get_controller_leader(self):
2237
 
        members = [
2238
 
            Machine('3', {}),
2239
 
            Machine('0', {}),
2240
 
            Machine('2', {}),
2241
 
        ]
2242
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2243
 
        with patch.object(client, 'get_controller_members', autospec=True,
2244
 
                          return_value=members):
2245
 
            leader = client.get_controller_leader()
2246
 
        self.assertEqual(Machine('3', {}), leader)
2247
 
 
2248
 
    def test_wait_for_ha(self):
2249
 
        value = yaml.safe_dump({
2250
 
            'machines': {
2251
 
                '0': {'controller-member-status': 'has-vote'},
2252
 
                '1': {'controller-member-status': 'has-vote'},
2253
 
                '2': {'controller-member-status': 'has-vote'},
2254
 
            },
2255
 
            'services': {},
2256
 
        })
2257
 
        client = EnvJujuClient(JujuData('local'), None, None)
2258
 
        with patch.object(client, 'get_juju_output',
2259
 
                          return_value=value) as gjo_mock:
2260
 
            client.wait_for_ha()
2261
 
        gjo_mock.assert_called_once_with(
2262
 
            'show-status', '--format', 'yaml', controller=True)
2263
 
 
2264
 
    def test_wait_for_ha_no_has_vote(self):
2265
 
        value = yaml.safe_dump({
2266
 
            'machines': {
2267
 
                '0': {'controller-member-status': 'no-vote'},
2268
 
                '1': {'controller-member-status': 'no-vote'},
2269
 
                '2': {'controller-member-status': 'no-vote'},
2270
 
            },
2271
 
            'services': {},
2272
 
        })
2273
 
        client = EnvJujuClient(JujuData('local'), None, None)
2274
 
        with patch.object(client, 'get_juju_output', return_value=value):
2275
 
            writes = []
2276
 
            with patch('jujupy.until_timeout', autospec=True,
2277
 
                       return_value=[2, 1]):
2278
 
                with patch.object(GroupReporter, '_write', autospec=True,
2279
 
                                  side_effect=lambda _, s: writes.append(s)):
2280
 
                    with self.assertRaisesRegexp(
2281
 
                            Exception,
2282
 
                            'Timed out waiting for voting to be enabled.'):
2283
 
                        client.wait_for_ha()
2284
 
            self.assertEqual(writes[:2], ['no-vote: 0, 1, 2', ' .'])
2285
 
            self.assertEqual(writes[2:-1], ['.'] * (len(writes) - 3))
2286
 
            self.assertEqual(writes[-1:], ['\n'])
2287
 
 
2288
 
    def test_wait_for_ha_timeout(self):
2289
 
        value = yaml.safe_dump({
2290
 
            'machines': {
2291
 
                '0': {'controller-member-status': 'has-vote'},
2292
 
                '1': {'controller-member-status': 'has-vote'},
2293
 
            },
2294
 
            'services': {},
2295
 
        })
2296
 
        client = EnvJujuClient(JujuData('local'), None, None)
2297
 
        with patch('jujupy.until_timeout', lambda x: range(0)):
2298
 
            with patch.object(client, 'get_juju_output', return_value=value):
2299
 
                with self.assertRaisesRegexp(
2300
 
                        Exception,
2301
 
                        'Timed out waiting for voting to be enabled.'):
2302
 
                    client.wait_for_ha()
2303
 
 
2304
 
    def test_wait_for_ha_timeout_with_status_error(self):
2305
 
        value = yaml.safe_dump({
2306
 
            'machines': {
2307
 
                '0': {'agent-state-info': 'running'},
2308
 
                '1': {'agent-state-info': 'error: foo'},
2309
 
            },
2310
 
            'services': {},
2311
 
        })
2312
 
        client = EnvJujuClient(JujuData('local'), None, None)
2313
 
        with patch('jujupy.until_timeout', autospec=True, return_value=[2, 1]):
2314
 
            with patch.object(client, 'get_juju_output', return_value=value):
2315
 
                with self.assertRaisesRegexp(
2316
 
                        ErroredUnit, '1 is in state error: foo'):
2317
 
                    client.wait_for_ha()
2318
 
 
2319
 
    def test_wait_for_ha_suppresses_deadline(self):
2320
 
        with self.only_status_checks(self.make_ha_status()) as client:
2321
 
            client.wait_for_ha()
2322
 
 
2323
 
    def test_wait_for_ha_checks_deadline(self):
2324
 
        with self.status_does_not_check(self.make_ha_status()) as client:
2325
 
            with self.assertRaises(SoftDeadlineExceeded):
2326
 
                client.wait_for_ha()
2327
 
 
2328
 
    def test_wait_for_deploy_started(self):
2329
 
        value = yaml.safe_dump({
2330
 
            'machines': {
2331
 
                '0': {'agent-state': 'started'},
2332
 
            },
2333
 
            'applications': {
2334
 
                'jenkins': {
2335
 
                    'units': {
2336
 
                        'jenkins/1': {'baz': 'qux'}
2337
 
                    }
2338
 
                }
2339
 
            }
2340
 
        })
2341
 
        client = EnvJujuClient(JujuData('local'), None, None)
2342
 
        with patch.object(client, 'get_juju_output', return_value=value):
2343
 
            client.wait_for_deploy_started()
2344
 
 
2345
 
    def test_wait_for_deploy_started_timeout(self):
2346
 
        value = yaml.safe_dump({
2347
 
            'machines': {
2348
 
                '0': {'agent-state': 'started'},
2349
 
            },
2350
 
            'applications': {},
2351
 
        })
2352
 
        client = EnvJujuClient(JujuData('local'), None, None)
2353
 
        with patch('jujupy.until_timeout', lambda x: range(0)):
2354
 
            with patch.object(client, 'get_juju_output', return_value=value):
2355
 
                with self.assertRaisesRegexp(
2356
 
                        Exception,
2357
 
                        'Timed out waiting for services to start.'):
2358
 
                    client.wait_for_deploy_started()
2359
 
 
2360
 
    def make_deployed_status(self):
2361
 
        return {
2362
 
            'machines': {
2363
 
                '0': {'agent-state': 'started'},
2364
 
            },
2365
 
            'applications': {
2366
 
                'jenkins': {
2367
 
                    'units': {
2368
 
                        'jenkins/1': {'baz': 'qux'}
2369
 
                    }
2370
 
                }
2371
 
            }
2372
 
        }
2373
 
 
2374
 
    def test_wait_for_deploy_started_suppresses_deadline(self):
2375
 
        with self.only_status_checks(self.make_deployed_status()) as client:
2376
 
            client.wait_for_deploy_started()
2377
 
 
2378
 
    def test_wait_for_deploy_started_checks_deadline(self):
2379
 
        with self.status_does_not_check(self.make_deployed_status()) as client:
2380
 
            with self.assertRaises(SoftDeadlineExceeded):
2381
 
                client.wait_for_deploy_started()
2382
 
 
2383
 
    def test_wait_for_version(self):
2384
 
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
2385
 
        client = EnvJujuClient(JujuData('local'), None, None)
2386
 
        with patch.object(client, 'get_juju_output', return_value=value):
2387
 
            client.wait_for_version('1.17.2')
2388
 
 
2389
 
    def test_wait_for_version_timeout(self):
2390
 
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.1')
2391
 
        client = EnvJujuClient(JujuData('local'), None, None)
2392
 
        writes = []
2393
 
        with patch('jujupy.until_timeout', lambda x, start=None: [x]):
2394
 
            with patch.object(client, 'get_juju_output', return_value=value):
2395
 
                with patch.object(GroupReporter, '_write', autospec=True,
2396
 
                                  side_effect=lambda _, s: writes.append(s)):
2397
 
                    with self.assertRaisesRegexp(
2398
 
                            Exception, 'Some versions did not update'):
2399
 
                        client.wait_for_version('1.17.2')
2400
 
        self.assertEqual(writes, ['1.17.1: jenkins/0', ' .', '\n'])
2401
 
 
2402
 
    def test_wait_for_version_handles_connection_error(self):
2403
 
        err = subprocess.CalledProcessError(2, 'foo')
2404
 
        err.stderr = 'Unable to connect to environment'
2405
 
        err = CannotConnectEnv(err)
2406
 
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
2407
 
        actions = [err, status]
2408
 
 
2409
 
        def get_juju_output_fake(*args, **kwargs):
2410
 
            action = actions.pop(0)
2411
 
            if isinstance(action, Exception):
2412
 
                raise action
2413
 
            else:
2414
 
                return action
2415
 
 
2416
 
        client = EnvJujuClient(JujuData('local'), None, None)
2417
 
        with patch.object(client, 'get_juju_output', get_juju_output_fake):
2418
 
            client.wait_for_version('1.17.2')
2419
 
 
2420
 
    def test_wait_for_version_raises_non_connection_error(self):
2421
 
        err = Exception('foo')
2422
 
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
2423
 
        actions = [err, status]
2424
 
 
2425
 
        def get_juju_output_fake(*args, **kwargs):
2426
 
            action = actions.pop(0)
2427
 
            if isinstance(action, Exception):
2428
 
                raise action
2429
 
            else:
2430
 
                return action
2431
 
 
2432
 
        client = EnvJujuClient(JujuData('local'), None, None)
2433
 
        with patch.object(client, 'get_juju_output', get_juju_output_fake):
2434
 
            with self.assertRaisesRegexp(Exception, 'foo'):
2435
 
                client.wait_for_version('1.17.2')
2436
 
 
2437
 
    def test_wait_for_just_machine_0(self):
2438
 
        value = yaml.safe_dump({
2439
 
            'machines': {
2440
 
                '0': {'agent-state': 'started'},
2441
 
            },
2442
 
        })
2443
 
        client = EnvJujuClient(JujuData('local'), None, None)
2444
 
        with patch.object(client, 'get_juju_output', return_value=value):
2445
 
            client.wait_for('machines-not-0', 'none')
2446
 
 
2447
 
    def test_wait_for_just_machine_0_timeout(self):
2448
 
        value = yaml.safe_dump({
2449
 
            'machines': {
2450
 
                '0': {'agent-state': 'started'},
2451
 
                '1': {'agent-state': 'started'},
2452
 
            },
2453
 
        })
2454
 
        client = EnvJujuClient(JujuData('local'), None, None)
2455
 
        with patch.object(client, 'get_juju_output', return_value=value), \
2456
 
            patch('jujupy.until_timeout', lambda x: range(0)), \
2457
 
            self.assertRaisesRegexp(
2458
 
                Exception,
2459
 
                'Timed out waiting for machines-not-0'):
2460
 
            client.wait_for('machines-not-0', 'none')
2461
 
 
2462
 
    def test_set_model_constraints(self):
2463
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
2464
 
        with patch.object(client, 'juju') as juju_mock:
2465
 
            client.set_model_constraints({'bar': 'baz'})
2466
 
        juju_mock.assert_called_once_with('set-model-constraints',
2467
 
                                          ('bar=baz',))
2468
 
 
2469
 
    def test_get_model_config(self):
2470
 
        env = JujuData('foo', None)
2471
 
        fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
2472
 
        client = EnvJujuClient(env, None, 'juju')
2473
 
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
2474
 
            result = client.get_model_config()
2475
 
        assert_juju_call(
2476
 
            self, po_mock, client, (
2477
 
                'juju', '--show-log',
2478
 
                'model-config', '-m', 'foo:foo', '--format', 'yaml'))
2479
 
        self.assertEqual({'bar': 'baz'}, result)
2480
 
 
2481
 
    def test_get_env_option(self):
2482
 
        env = JujuData('foo', None)
2483
 
        fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
2484
 
        client = EnvJujuClient(env, None, 'juju')
2485
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
2486
 
            result = client.get_env_option('tools-metadata-url')
2487
 
        self.assertEqual(
2488
 
            mock.call_args[0][0],
2489
 
            ('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2490
 
             'tools-metadata-url'))
2491
 
        self.assertEqual('https://example.org/juju/tools', result)
2492
 
 
2493
 
    def test_set_env_option(self):
2494
 
        env = JujuData('foo')
2495
 
        client = EnvJujuClient(env, None, 'juju')
2496
 
        with patch('subprocess.check_call') as mock:
2497
 
            client.set_env_option(
2498
 
                'tools-metadata-url', 'https://example.org/juju/tools')
2499
 
        environ = dict(os.environ)
2500
 
        environ['JUJU_HOME'] = client.env.juju_home
2501
 
        mock.assert_called_with(
2502
 
            ('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2503
 
             'tools-metadata-url=https://example.org/juju/tools'))
2504
 
 
2505
 
    def test_unset_env_option(self):
2506
 
        env = JujuData('foo')
2507
 
        client = EnvJujuClient(env, None, 'juju')
2508
 
        with patch('subprocess.check_call') as mock:
2509
 
            client.unset_env_option('tools-metadata-url')
2510
 
        environ = dict(os.environ)
2511
 
        environ['JUJU_HOME'] = client.env.juju_home
2512
 
        mock.assert_called_with(
2513
 
            ('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2514
 
             '--reset', 'tools-metadata-url'))
2515
 
 
2516
 
    def test_set_testing_agent_metadata_url(self):
2517
 
        env = JujuData(None, {'type': 'foo'})
2518
 
        client = EnvJujuClient(env, None, None)
2519
 
        with patch.object(client, 'get_env_option') as mock_get:
2520
 
            mock_get.return_value = 'https://example.org/juju/tools'
2521
 
            with patch.object(client, 'set_env_option') as mock_set:
2522
 
                client.set_testing_agent_metadata_url()
2523
 
        mock_get.assert_called_with('agent-metadata-url')
2524
 
        mock_set.assert_called_with(
2525
 
            'agent-metadata-url',
2526
 
            'https://example.org/juju/testing/tools')
2527
 
 
2528
 
    def test_set_testing_agent_metadata_url_noop(self):
2529
 
        env = JujuData(None, {'type': 'foo'})
2530
 
        client = EnvJujuClient(env, None, None)
2531
 
        with patch.object(client, 'get_env_option') as mock_get:
2532
 
            mock_get.return_value = 'https://example.org/juju/testing/tools'
2533
 
            with patch.object(client, 'set_env_option') as mock_set:
2534
 
                client.set_testing_agent_metadata_url()
2535
 
        mock_get.assert_called_with('agent-metadata-url',)
2536
 
        self.assertEqual(0, mock_set.call_count)
2537
 
 
2538
 
    def test_juju(self):
2539
 
        env = JujuData('qux')
2540
 
        client = EnvJujuClient(env, None, 'juju')
2541
 
        with patch('subprocess.check_call') as mock:
2542
 
            client.juju('foo', ('bar', 'baz'))
2543
 
        environ = dict(os.environ)
2544
 
        environ['JUJU_HOME'] = client.env.juju_home
2545
 
        mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux',
2546
 
                                 'bar', 'baz'))
2547
 
 
2548
 
    def test_expect_returns_pexpect_spawn_object(self):
2549
 
        env = JujuData('qux')
2550
 
        client = EnvJujuClient(env, None, 'juju')
2551
 
        with patch('pexpect.spawn') as mock:
2552
 
            process = client.expect('foo', ('bar', 'baz'))
2553
 
 
2554
 
        self.assertIs(process, mock.return_value)
2555
 
        mock.assert_called_once_with('juju --show-log foo -m qux:qux bar baz')
2556
 
 
2557
 
    def test_expect_uses_provided_envvar_path(self):
2558
 
        from pexpect import ExceptionPexpect
2559
 
        env = JujuData('qux')
2560
 
        client = EnvJujuClient(env, None, 'juju')
2561
 
 
2562
 
        with temp_dir() as empty_path:
2563
 
            broken_envvars = dict(PATH=empty_path)
2564
 
            self.assertRaises(
2565
 
                ExceptionPexpect,
2566
 
                client.expect,
2567
 
                'ls', (), extra_env=broken_envvars,
2568
 
                )
2569
 
 
2570
 
    def test_juju_env(self):
2571
 
        env = JujuData('qux')
2572
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2573
 
 
2574
 
        def check_path(*args, **kwargs):
2575
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
2576
 
        with patch('subprocess.check_call', side_effect=check_path):
2577
 
            client.juju('foo', ('bar', 'baz'))
2578
 
 
2579
 
    def test_juju_no_check(self):
2580
 
        env = JujuData('qux')
2581
 
        client = EnvJujuClient(env, None, 'juju')
2582
 
        environ = dict(os.environ)
2583
 
        environ['JUJU_HOME'] = client.env.juju_home
2584
 
        with patch('subprocess.call') as mock:
2585
 
            client.juju('foo', ('bar', 'baz'), check=False)
2586
 
        mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux',
2587
 
                                 'bar', 'baz'))
2588
 
 
2589
 
    def test_juju_no_check_env(self):
2590
 
        env = JujuData('qux')
2591
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2592
 
 
2593
 
        def check_path(*args, **kwargs):
2594
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
2595
 
        with patch('subprocess.call', side_effect=check_path):
2596
 
            client.juju('foo', ('bar', 'baz'), check=False)
2597
 
 
2598
 
    def test_juju_timeout(self):
2599
 
        env = JujuData('qux')
2600
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2601
 
        with patch('subprocess.check_call') as cc_mock:
2602
 
            client.juju('foo', ('bar', 'baz'), timeout=58)
2603
 
        self.assertEqual(cc_mock.call_args[0][0], (
2604
 
            sys.executable, get_timeout_path(), '58.00', '--', 'baz',
2605
 
            '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz'))
2606
 
 
2607
 
    def test_juju_juju_home(self):
2608
 
        env = JujuData('qux')
2609
 
        os.environ['JUJU_HOME'] = 'foo'
2610
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2611
 
 
2612
 
        def check_home(*args, **kwargs):
2613
 
            self.assertEqual(os.environ['JUJU_HOME'], 'foo')
2614
 
            yield
2615
 
            self.assertEqual(os.environ['JUJU_HOME'], 'asdf')
2616
 
            yield
2617
 
 
2618
 
        with patch('subprocess.check_call', side_effect=check_home):
2619
 
            client.juju('foo', ('bar', 'baz'))
2620
 
            client.env.juju_home = 'asdf'
2621
 
            client.juju('foo', ('bar', 'baz'))
2622
 
 
2623
 
    def test_juju_extra_env(self):
2624
 
        env = JujuData('qux')
2625
 
        client = EnvJujuClient(env, None, 'juju')
2626
 
        extra_env = {'JUJU': '/juju', 'JUJU_HOME': client.env.juju_home}
2627
 
 
2628
 
        def check_env(*args, **kwargs):
2629
 
            self.assertEqual('/juju', os.environ['JUJU'])
2630
 
 
2631
 
        with patch('subprocess.check_call', side_effect=check_env) as mock:
2632
 
            client.juju('quickstart', ('bar', 'baz'), extra_env=extra_env)
2633
 
        mock.assert_called_with(
2634
 
            ('juju', '--show-log', 'quickstart', '-m', 'qux:qux',
2635
 
             'bar', 'baz'))
2636
 
 
2637
 
    def test_juju_backup_with_tgz(self):
2638
 
        env = JujuData('qux')
2639
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2640
 
 
2641
 
        with patch(
2642
 
                'subprocess.Popen',
2643
 
                return_value=FakePopen('foojuju-backup-24.tgzz', '', 0),
2644
 
                ) as popen_mock:
2645
 
            backup_file = client.backup()
2646
 
        self.assertEqual(backup_file, os.path.abspath('juju-backup-24.tgz'))
2647
 
        assert_juju_call(self, popen_mock, client, ('baz', '--show-log',
2648
 
                         'create-backup', '-m', 'qux:qux'))
2649
 
 
2650
 
    def test_juju_backup_with_tar_gz(self):
2651
 
        env = JujuData('qux')
2652
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2653
 
        with patch('subprocess.Popen',
2654
 
                   return_value=FakePopen(
2655
 
                       'foojuju-backup-123-456.tar.gzbar', '', 0)):
2656
 
            backup_file = client.backup()
2657
 
        self.assertEqual(
2658
 
            backup_file, os.path.abspath('juju-backup-123-456.tar.gz'))
2659
 
 
2660
 
    def test_juju_backup_no_file(self):
2661
 
        env = JujuData('qux')
2662
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2663
 
        with patch('subprocess.Popen', return_value=FakePopen('', '', 0)):
2664
 
            with self.assertRaisesRegexp(
2665
 
                    Exception, 'The backup file was not found in output'):
2666
 
                client.backup()
2667
 
 
2668
 
    def test_juju_backup_wrong_file(self):
2669
 
        env = JujuData('qux')
2670
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2671
 
        with patch('subprocess.Popen',
2672
 
                   return_value=FakePopen('mumu-backup-24.tgz', '', 0)):
2673
 
            with self.assertRaisesRegexp(
2674
 
                    Exception, 'The backup file was not found in output'):
2675
 
                client.backup()
2676
 
 
2677
 
    def test_juju_backup_environ(self):
2678
 
        env = JujuData('qux')
2679
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2680
 
        environ = client._shell_environ()
2681
 
 
2682
 
        def side_effect(*args, **kwargs):
2683
 
            self.assertEqual(environ, os.environ)
2684
 
            return FakePopen('foojuju-backup-123-456.tar.gzbar', '', 0)
2685
 
        with patch('subprocess.Popen', side_effect=side_effect):
2686
 
            client.backup()
2687
 
            self.assertNotEqual(environ, os.environ)
2688
 
 
2689
 
    def test_restore_backup(self):
2690
 
        env = JujuData('qux')
2691
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2692
 
        with patch.object(client, 'juju') as gjo_mock:
2693
 
            client.restore_backup('quxx')
2694
 
        gjo_mock.assert_called_once_with(
2695
 
            'restore-backup',
2696
 
            ('-b', '--constraints', 'mem=2G', '--file', 'quxx'))
2697
 
 
2698
 
    def test_restore_backup_async(self):
2699
 
        env = JujuData('qux')
2700
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2701
 
        with patch.object(client, 'juju_async') as gjo_mock:
2702
 
            result = client.restore_backup_async('quxx')
2703
 
        gjo_mock.assert_called_once_with('restore-backup', (
2704
 
            '-b', '--constraints', 'mem=2G', '--file', 'quxx'))
2705
 
        self.assertIs(gjo_mock.return_value, result)
2706
 
 
2707
 
    def test_enable_ha(self):
2708
 
        env = JujuData('qux')
2709
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2710
 
        with patch.object(client, 'juju', autospec=True) as eha_mock:
2711
 
            client.enable_ha()
2712
 
        eha_mock.assert_called_once_with('enable-ha', ('-n', '3'))
2713
 
 
2714
 
    def test_juju_async(self):
2715
 
        env = JujuData('qux')
2716
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2717
 
        with patch('subprocess.Popen') as popen_class_mock:
2718
 
            with client.juju_async('foo', ('bar', 'baz')) as proc:
2719
 
                assert_juju_call(
2720
 
                    self,
2721
 
                    popen_class_mock,
2722
 
                    client,
2723
 
                    ('baz', '--show-log', 'foo', '-m', 'qux:qux',
2724
 
                     'bar', 'baz'))
2725
 
                self.assertIs(proc, popen_class_mock.return_value)
2726
 
                self.assertEqual(proc.wait.call_count, 0)
2727
 
                proc.wait.return_value = 0
2728
 
        proc.wait.assert_called_once_with()
2729
 
 
2730
 
    def test_juju_async_failure(self):
2731
 
        env = JujuData('qux')
2732
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2733
 
        with patch('subprocess.Popen') as popen_class_mock:
2734
 
            with self.assertRaises(subprocess.CalledProcessError) as err_cxt:
2735
 
                with client.juju_async('foo', ('bar', 'baz')):
2736
 
                    proc_mock = popen_class_mock.return_value
2737
 
                    proc_mock.wait.return_value = 23
2738
 
        self.assertEqual(err_cxt.exception.returncode, 23)
2739
 
        self.assertEqual(err_cxt.exception.cmd, (
2740
 
            'baz', '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz'))
2741
 
 
2742
 
    def test_juju_async_environ(self):
2743
 
        env = JujuData('qux')
2744
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2745
 
        environ = client._shell_environ()
2746
 
        proc_mock = Mock()
2747
 
        with patch('subprocess.Popen') as popen_class_mock:
2748
 
 
2749
 
            def check_environ(*args, **kwargs):
2750
 
                self.assertEqual(environ, os.environ)
2751
 
                return proc_mock
2752
 
            popen_class_mock.side_effect = check_environ
2753
 
            proc_mock.wait.return_value = 0
2754
 
            with client.juju_async('foo', ('bar', 'baz')):
2755
 
                pass
2756
 
            self.assertNotEqual(environ, os.environ)
2757
 
 
2758
 
    def test_is_jes_enabled(self):
2759
 
        # EnvJujuClient knows that JES is always enabled, and doesn't need to
2760
 
        # shell out.
2761
 
        env = JujuData('qux')
2762
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2763
 
        fake_popen = FakePopen(' %s' % SYSTEM, None, 0)
2764
 
        with patch('subprocess.Popen',
2765
 
                   return_value=fake_popen) as po_mock:
2766
 
            self.assertTrue(client.is_jes_enabled())
2767
 
        self.assertEqual(0, po_mock.call_count)
2768
 
 
2769
 
    def test_get_jes_command(self):
2770
 
        env = JujuData('qux')
2771
 
        client = EnvJujuClient(env, None, '/foobar/baz')
2772
 
        # Juju 1.24 and older do not have a JES command. It is an error
2773
 
        # to call get_jes_command when is_jes_enabled is False
2774
 
        fake_popen = FakePopen(' %s' % SYSTEM, None, 0)
2775
 
        with patch('subprocess.Popen',
2776
 
                   return_value=fake_popen) as po_mock:
2777
 
            self.assertEqual(KILL_CONTROLLER, client.get_jes_command())
2778
 
        self.assertEqual(0, po_mock.call_count)
2779
 
 
2780
 
    def test_get_juju_timings(self):
2781
 
        env = JujuData('foo')
2782
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
2783
 
        client._backend.juju_timings = {("juju", "op1"): [1],
2784
 
                                        ("juju", "op2"): [2]}
2785
 
        flattened_timings = client.get_juju_timings()
2786
 
        expected = {"juju op1": [1], "juju op2": [2]}
2787
 
        self.assertEqual(flattened_timings, expected)
2788
 
 
2789
 
    def test_deployer(self):
2790
 
        client = EnvJujuClient(JujuData('foo', {'type': 'local'}),
2791
 
                               '1.23-series-arch', None)
2792
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2793
 
            client.deployer('bundle:~juju-qa/some-bundle')
2794
 
        mock.assert_called_with(
2795
 
            'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay',
2796
 
                         '10', '--timeout', '3600', '--config',
2797
 
                         'bundle:~juju-qa/some-bundle'),
2798
 
            True, include_e=False
2799
 
        )
2800
 
 
2801
 
    def test_deployer_with_bundle_name(self):
2802
 
        client = EnvJujuClient(JujuData('foo', {'type': 'local'}),
2803
 
                               '2.0.0-series-arch', None)
2804
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2805
 
            client.deployer('bundle:~juju-qa/some-bundle', 'name')
2806
 
        mock.assert_called_with(
2807
 
            'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay',
2808
 
                         '10', '--timeout', '3600', '--config',
2809
 
                         'bundle:~juju-qa/some-bundle', 'name'),
2810
 
            True, include_e=False
2811
 
        )
2812
 
 
2813
 
    def test_quickstart_maas(self):
2814
 
        client = EnvJujuClient(JujuData(None, {'type': 'maas'}),
2815
 
                               '1.23-series-arch', '/juju')
2816
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2817
 
            client.quickstart('bundle:~juju-qa/some-bundle')
2818
 
        mock.assert_called_with(
2819
 
            'quickstart',
2820
 
            ('--constraints', 'mem=2G', '--no-browser',
2821
 
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
2822
 
        )
2823
 
 
2824
 
    def test_quickstart_local(self):
2825
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2826
 
                               '1.23-series-arch', '/juju')
2827
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2828
 
            client.quickstart('bundle:~juju-qa/some-bundle')
2829
 
        mock.assert_called_with(
2830
 
            'quickstart',
2831
 
            ('--constraints', 'mem=2G', '--no-browser',
2832
 
             'bundle:~juju-qa/some-bundle'), True, extra_env={'JUJU': '/juju'}
2833
 
        )
2834
 
 
2835
 
    def test_quickstart_nonlocal(self):
2836
 
        client = EnvJujuClient(JujuData(None, {'type': 'nonlocal'}),
2837
 
                               '1.23-series-arch', '/juju')
2838
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2839
 
            client.quickstart('bundle:~juju-qa/some-bundle')
2840
 
        mock.assert_called_with(
2841
 
            'quickstart',
2842
 
            ('--constraints', 'mem=2G', '--no-browser',
2843
 
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
2844
 
        )
2845
 
 
2846
 
    def test_quickstart_template(self):
2847
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2848
 
                               '1.23-series-arch', '/juju')
2849
 
        with patch.object(EnvJujuClient, 'juju') as mock:
2850
 
            client.quickstart('bundle:~juju-qa/some-{container}-bundle')
2851
 
        mock.assert_called_with(
2852
 
            'quickstart', (
2853
 
                '--constraints', 'mem=2G', '--no-browser',
2854
 
                'bundle:~juju-qa/some-lxd-bundle'),
2855
 
            True, extra_env={'JUJU': '/juju'})
2856
 
 
2857
 
    def test_action_do(self):
2858
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2859
 
                               '1.23-series-arch', None)
2860
 
        with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2861
 
            mock.return_value = \
2862
 
                "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9"
2863
 
            id = client.action_do("foo/0", "myaction", "param=5")
2864
 
            self.assertEqual(id, "5a92ec93-d4be-4399-82dc-7431dbfd08f9")
2865
 
        mock.assert_called_once_with(
2866
 
            'run-action', 'foo/0', 'myaction', "param=5"
2867
 
        )
2868
 
 
2869
 
    def test_action_do_error(self):
2870
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2871
 
                               '1.23-series-arch', None)
2872
 
        with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2873
 
            mock.return_value = "some bad text"
2874
 
            with self.assertRaisesRegexp(Exception,
2875
 
                                         "Action id not found in output"):
2876
 
                client.action_do("foo/0", "myaction", "param=5")
2877
 
 
2878
 
    def test_action_fetch(self):
2879
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2880
 
                               '1.23-series-arch', None)
2881
 
        with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2882
 
            ret = "status: completed\nfoo: bar"
2883
 
            mock.return_value = ret
2884
 
            out = client.action_fetch("123")
2885
 
            self.assertEqual(out, ret)
2886
 
        mock.assert_called_once_with(
2887
 
            'show-action-output', '123', "--wait", "1m"
2888
 
        )
2889
 
 
2890
 
    def test_action_fetch_timeout(self):
2891
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2892
 
                               '1.23-series-arch', None)
2893
 
        ret = "status: pending\nfoo: bar"
2894
 
        with patch.object(EnvJujuClient,
2895
 
                          'get_juju_output', return_value=ret):
2896
 
            with self.assertRaisesRegexp(Exception,
2897
 
                                         "timed out waiting for action"):
2898
 
                client.action_fetch("123")
2899
 
 
2900
 
    def test_action_do_fetch(self):
2901
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2902
 
                               '1.23-series-arch', None)
2903
 
        with patch.object(EnvJujuClient, 'get_juju_output') as mock:
2904
 
            ret = "status: completed\nfoo: bar"
2905
 
            # setting side_effect to an iterable will return the next value
2906
 
            # from the list each time the function is called.
2907
 
            mock.side_effect = [
2908
 
                "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9",
2909
 
                ret]
2910
 
            out = client.action_do_fetch("foo/0", "myaction", "param=5")
2911
 
            self.assertEqual(out, ret)
2912
 
 
2913
 
    def test_run(self):
2914
 
        client = fake_juju_client(cls=EnvJujuClient)
2915
 
        run_list = [
2916
 
            {"MachineId": "1",
2917
 
             "Stdout": "Linux\n",
2918
 
             "ReturnCode": 255,
2919
 
             "Stderr": "Permission denied (publickey,password)"}]
2920
 
        run_output = json.dumps(run_list)
2921
 
        with patch.object(client._backend, 'get_juju_output',
2922
 
                          return_value=run_output) as gjo_mock:
2923
 
            result = client.run(('wname',), applications=['foo', 'bar'])
2924
 
        self.assertEqual(run_list, result)
2925
 
        gjo_mock.assert_called_once_with(
2926
 
            'run', ('--format', 'json', '--application', 'foo,bar', 'wname'),
2927
 
            frozenset(
2928
 
                ['address-allocation', 'migration']), 'foo',
2929
 
            'name:name', user_name=None)
2930
 
 
2931
 
    def test_list_space(self):
2932
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2933
 
                               '1.23-series-arch', None)
2934
 
        yaml_dict = {'foo': 'bar'}
2935
 
        output = yaml.safe_dump(yaml_dict)
2936
 
        with patch.object(client, 'get_juju_output', return_value=output,
2937
 
                          autospec=True) as gjo_mock:
2938
 
            result = client.list_space()
2939
 
        self.assertEqual(result, yaml_dict)
2940
 
        gjo_mock.assert_called_once_with('list-space')
2941
 
 
2942
 
    def test_add_space(self):
2943
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2944
 
                               '1.23-series-arch', None)
2945
 
        with patch.object(client, 'juju', autospec=True) as juju_mock:
2946
 
            client.add_space('foo-space')
2947
 
        juju_mock.assert_called_once_with('add-space', ('foo-space'))
2948
 
 
2949
 
    def test_add_subnet(self):
2950
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2951
 
                               '1.23-series-arch', None)
2952
 
        with patch.object(client, 'juju', autospec=True) as juju_mock:
2953
 
            client.add_subnet('bar-subnet', 'foo-space')
2954
 
        juju_mock.assert_called_once_with('add-subnet',
2955
 
                                          ('bar-subnet', 'foo-space'))
2956
 
 
2957
 
    def test__shell_environ_uses_pathsep(self):
2958
 
        client = EnvJujuClient(JujuData('foo'), None, 'foo/bar/juju')
2959
 
        with patch('os.pathsep', '!'):
2960
 
            environ = client._shell_environ()
2961
 
        self.assertRegexpMatches(environ['PATH'], r'foo/bar\!')
2962
 
 
2963
 
    def test_set_config(self):
2964
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
2965
 
        with patch.object(client, 'juju') as juju_mock:
2966
 
            client.set_config('foo', {'bar': 'baz'})
2967
 
        juju_mock.assert_called_once_with('config', ('foo', 'bar=baz'))
2968
 
 
2969
 
    def test_get_config(self):
2970
 
        def output(*args, **kwargs):
2971
 
            return yaml.safe_dump({
2972
 
                'charm': 'foo',
2973
 
                'service': 'foo',
2974
 
                'settings': {
2975
 
                    'dir': {
2976
 
                        'default': 'true',
2977
 
                        'description': 'bla bla',
2978
 
                        'type': 'string',
2979
 
                        'value': '/tmp/charm-dir',
2980
 
                    }
2981
 
                }
2982
 
            })
2983
 
        expected = yaml.safe_load(output())
2984
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
2985
 
        with patch.object(client, 'get_juju_output',
2986
 
                          side_effect=output) as gjo_mock:
2987
 
            results = client.get_config('foo')
2988
 
        self.assertEqual(expected, results)
2989
 
        gjo_mock.assert_called_once_with('config', 'foo')
2990
 
 
2991
 
    def test_get_service_config(self):
2992
 
        def output(*args, **kwargs):
2993
 
            return yaml.safe_dump({
2994
 
                'charm': 'foo',
2995
 
                'service': 'foo',
2996
 
                'settings': {
2997
 
                    'dir': {
2998
 
                        'default': 'true',
2999
 
                        'description': 'bla bla',
3000
 
                        'type': 'string',
3001
 
                        'value': '/tmp/charm-dir',
3002
 
                    }
3003
 
                }
3004
 
            })
3005
 
        expected = yaml.safe_load(output())
3006
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
3007
 
        with patch.object(client, 'get_juju_output', side_effect=output):
3008
 
            results = client.get_service_config('foo')
3009
 
        self.assertEqual(expected, results)
3010
 
 
3011
 
    def test_get_service_config_timesout(self):
3012
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
3013
 
        with patch('jujupy.until_timeout', return_value=range(0)):
3014
 
            with self.assertRaisesRegexp(
3015
 
                    Exception, 'Timed out waiting for juju get'):
3016
 
                client.get_service_config('foo')
3017
 
 
3018
 
    def test_upgrade_mongo(self):
3019
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
3020
 
        with patch.object(client, 'juju') as juju_mock:
3021
 
            client.upgrade_mongo()
3022
 
        juju_mock.assert_called_once_with('upgrade-mongo', ())
3023
 
 
3024
 
    def test_enable_feature(self):
3025
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
3026
 
        self.assertEqual(set(), client.feature_flags)
3027
 
        client.enable_feature('actions')
3028
 
        self.assertEqual(set(['actions']), client.feature_flags)
3029
 
 
3030
 
    def test_enable_feature_invalid(self):
3031
 
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
3032
 
        self.assertEqual(set(), client.feature_flags)
3033
 
        with self.assertRaises(ValueError) as ctx:
3034
 
            client.enable_feature('nomongo')
3035
 
        self.assertEqual(str(ctx.exception), "Unknown feature flag: 'nomongo'")
3036
 
 
3037
 
    def test_is_juju1x(self):
3038
 
        client = EnvJujuClient(None, '1.25.5', None)
3039
 
        self.assertTrue(client.is_juju1x())
3040
 
 
3041
 
    def test_is_juju1x_false(self):
3042
 
        client = EnvJujuClient(None, '2.0.0', None)
3043
 
        self.assertFalse(client.is_juju1x())
3044
 
 
3045
 
    def test__get_register_command_returns_register_token(self):
3046
 
        output = dedent("""\
3047
 
        User "x" added
3048
 
        User "x" granted read access to model "y"
3049
 
        Please send this command to x:
3050
 
            juju register AaBbCc""")
3051
 
        output_cmd = 'AaBbCc'
3052
 
        fake_client = fake_juju_client()
3053
 
 
3054
 
        register_cmd = fake_client._get_register_command(output)
3055
 
        self.assertEqual(register_cmd, output_cmd)
3056
 
 
3057
 
    def test_revoke(self):
3058
 
        fake_client = fake_juju_client()
3059
 
        username = 'fakeuser'
3060
 
        model = 'foo'
3061
 
        default_permissions = 'read'
3062
 
        default_model = fake_client.model_name
3063
 
        default_controller = fake_client.env.controller.name
3064
 
 
3065
 
        with patch.object(fake_client, 'juju', return_value=True):
3066
 
            fake_client.revoke(username)
3067
 
            fake_client.juju.assert_called_with('revoke',
3068
 
                                                ('-c', default_controller,
3069
 
                                                 username, default_permissions,
3070
 
                                                 default_model),
3071
 
                                                include_e=False)
3072
 
 
3073
 
            fake_client.revoke(username, model)
3074
 
            fake_client.juju.assert_called_with('revoke',
3075
 
                                                ('-c', default_controller,
3076
 
                                                 username, default_permissions,
3077
 
                                                 model),
3078
 
                                                include_e=False)
3079
 
 
3080
 
            fake_client.revoke(username, model, permissions='write')
3081
 
            fake_client.juju.assert_called_with('revoke',
3082
 
                                                ('-c', default_controller,
3083
 
                                                 username, 'write', model),
3084
 
                                                include_e=False)
3085
 
 
3086
 
    def test_add_user_perms(self):
3087
 
        fake_client = fake_juju_client()
3088
 
        username = 'fakeuser'
3089
 
 
3090
 
        # Ensure add_user returns expected value.
3091
 
        self.assertEqual(
3092
 
            fake_client.add_user_perms(username),
3093
 
            get_user_register_token(username))
3094
 
 
3095
 
    @staticmethod
3096
 
    def assert_add_user_perms(model, permissions):
3097
 
        fake_client = fake_juju_client()
3098
 
        username = 'fakeuser'
3099
 
        output = get_user_register_command_info(username)
3100
 
        if permissions is None:
3101
 
            permissions = 'login'
3102
 
        expected_args = [username, '-c', fake_client.env.controller.name]
3103
 
        with patch.object(fake_client, 'get_juju_output',
3104
 
                          return_value=output) as get_output:
3105
 
            with patch.object(fake_client, 'juju') as mock_juju:
3106
 
                fake_client.add_user_perms(username, model, permissions)
3107
 
                if model is None:
3108
 
                    model = fake_client.env.environment
3109
 
                get_output.assert_called_with(
3110
 
                    'add-user', *expected_args, include_e=False)
3111
 
                if permissions == 'login':
3112
 
                    mock_juju.assert_called_once_with(
3113
 
                        'grant',
3114
 
                        ('fakeuser', permissions,
3115
 
                         '-c', fake_client.env.controller.name),
3116
 
                        include_e=False)
3117
 
                else:
3118
 
                    mock_juju.assert_called_once_with(
3119
 
                        'grant',
3120
 
                        ('fakeuser', permissions,
3121
 
                         model,
3122
 
                         '-c', fake_client.env.controller.name),
3123
 
                        include_e=False)
3124
 
 
3125
 
    def test_assert_add_user_permissions(self):
3126
 
        model = 'foo'
3127
 
        permissions = 'write'
3128
 
 
3129
 
        # Check using default model and permissions
3130
 
        self.assert_add_user_perms(None, None)
3131
 
 
3132
 
        # Check explicit model & default permissions
3133
 
        self.assert_add_user_perms(model, None)
3134
 
 
3135
 
        # Check explicit model & permissions
3136
 
        self.assert_add_user_perms(model, permissions)
3137
 
 
3138
 
        # Check default model & explicit permissions
3139
 
        self.assert_add_user_perms(None, permissions)
3140
 
 
3141
 
    def test_disable_user(self):
3142
 
        env = JujuData('foo')
3143
 
        username = 'fakeuser'
3144
 
        client = EnvJujuClient(env, None, None)
3145
 
        with patch.object(client, 'juju') as mock:
3146
 
            client.disable_user(username)
3147
 
        mock.assert_called_with(
3148
 
            'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
3149
 
 
3150
 
    def test_enable_user(self):
3151
 
        env = JujuData('foo')
3152
 
        username = 'fakeuser'
3153
 
        client = EnvJujuClient(env, None, None)
3154
 
        with patch.object(client, 'juju') as mock:
3155
 
            client.enable_user(username)
3156
 
        mock.assert_called_with(
3157
 
            'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
3158
 
 
3159
 
    def test_logout(self):
3160
 
        env = JujuData('foo')
3161
 
        client = EnvJujuClient(env, None, None)
3162
 
        with patch.object(client, 'juju') as mock:
3163
 
            client.logout()
3164
 
        mock.assert_called_with(
3165
 
            'logout', ('-c', 'foo'), include_e=False)
3166
 
 
3167
 
    def test_create_cloned_environment(self):
3168
 
        fake_client = fake_juju_client()
3169
 
        fake_client.bootstrap()
3170
 
        # fake_client_environ = fake_client._shell_environ()
3171
 
        controller_name = 'user_controller'
3172
 
        cloned = fake_client.create_cloned_environment(
3173
 
            'fakehome',
3174
 
            controller_name
3175
 
        )
3176
 
        self.assertIs(fake_client.__class__, type(cloned))
3177
 
        self.assertEqual(cloned.env.juju_home, 'fakehome')
3178
 
        self.assertEqual(cloned.env.controller.name, controller_name)
3179
 
        self.assertEqual(fake_client.env.controller.name, 'name')
3180
 
 
3181
 
    def test_list_clouds(self):
3182
 
        env = JujuData('foo')
3183
 
        client = EnvJujuClient(env, None, None)
3184
 
        with patch.object(client, 'get_juju_output') as mock:
3185
 
            client.list_clouds()
3186
 
        mock.assert_called_with(
3187
 
            'list-clouds', '--format', 'json', include_e=False)
3188
 
 
3189
 
    def test_show_controller(self):
3190
 
        env = JujuData('foo')
3191
 
        client = EnvJujuClient(env, None, None)
3192
 
        with patch.object(client, 'get_juju_output') as mock:
3193
 
            client.show_controller()
3194
 
        mock.assert_called_with(
3195
 
            'show-controller', '--format', 'json', include_e=False)
3196
 
 
3197
 
    def test_ssh_keys(self):
3198
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3199
 
        given_output = 'ssh keys output'
3200
 
        with patch.object(client, 'get_juju_output', autospec=True,
3201
 
                          return_value=given_output) as mock:
3202
 
            output = client.ssh_keys()
3203
 
        self.assertEqual(output, given_output)
3204
 
        mock.assert_called_once_with('ssh-keys')
3205
 
 
3206
 
    def test_ssh_keys_full(self):
3207
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3208
 
        given_output = 'ssh keys full output'
3209
 
        with patch.object(client, 'get_juju_output', autospec=True,
3210
 
                          return_value=given_output) as mock:
3211
 
            output = client.ssh_keys(full=True)
3212
 
        self.assertEqual(output, given_output)
3213
 
        mock.assert_called_once_with('ssh-keys', '--full')
3214
 
 
3215
 
    def test_add_ssh_key(self):
3216
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3217
 
        with patch.object(client, 'get_juju_output', autospec=True,
3218
 
                          return_value='') as mock:
3219
 
            output = client.add_ssh_key('ak', 'bk')
3220
 
        self.assertEqual(output, '')
3221
 
        mock.assert_called_once_with(
3222
 
            'add-ssh-key', 'ak', 'bk', merge_stderr=True)
3223
 
 
3224
 
    def test_remove_ssh_key(self):
3225
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3226
 
        with patch.object(client, 'get_juju_output', autospec=True,
3227
 
                          return_value='') as mock:
3228
 
            output = client.remove_ssh_key('ak', 'bk')
3229
 
        self.assertEqual(output, '')
3230
 
        mock.assert_called_once_with(
3231
 
            'remove-ssh-key', 'ak', 'bk', merge_stderr=True)
3232
 
 
3233
 
    def test_import_ssh_key(self):
3234
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3235
 
        with patch.object(client, 'get_juju_output', autospec=True,
3236
 
                          return_value='') as mock:
3237
 
            output = client.import_ssh_key('gh:au', 'lp:bu')
3238
 
        self.assertEqual(output, '')
3239
 
        mock.assert_called_once_with(
3240
 
            'import-ssh-key', 'gh:au', 'lp:bu', merge_stderr=True)
3241
 
 
3242
 
    def test_list_disabled_commands(self):
3243
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3244
 
        with patch.object(client, 'get_juju_output', autospec=True,
3245
 
                          return_value=dedent("""\
3246
 
             - command-set: destroy-model
3247
 
               message: Lock Models
3248
 
             - command-set: remove-object""")) as mock:
3249
 
            output = client.list_disabled_commands()
3250
 
        self.assertEqual([{'command-set': 'destroy-model',
3251
 
                           'message': 'Lock Models'},
3252
 
                          {'command-set': 'remove-object'}], output)
3253
 
        mock.assert_called_once_with('list-disabled-commands',
3254
 
                                     '--format', 'yaml')
3255
 
 
3256
 
    def test_disable_command(self):
3257
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3258
 
        with patch.object(client, 'juju', autospec=True) as mock:
3259
 
            client.disable_command(('all', 'message'))
3260
 
        mock.assert_called_once_with('disable-command', ('all', 'message'))
3261
 
 
3262
 
    def test_enable_command(self):
3263
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3264
 
        with patch.object(client, 'juju', autospec=True) as mock:
3265
 
            client.enable_command('all')
3266
 
        mock.assert_called_once_with('enable-command', 'all')
3267
 
 
3268
 
    def test_sync_tools(self):
3269
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3270
 
        with patch.object(client, 'juju', autospec=True) as mock:
3271
 
            client.sync_tools()
3272
 
        mock.assert_called_once_with('sync-tools', ())
3273
 
 
3274
 
    def test_sync_tools_local_dir(self):
3275
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3276
 
        with patch.object(client, 'juju', autospec=True) as mock:
3277
 
            client.sync_tools('/agents')
3278
 
        mock.assert_called_once_with('sync-tools', ('--local-dir', '/agents'),
3279
 
                                     include_e=False)
3280
 
 
3281
 
 
3282
 
class TestEnvJujuClient2B8(ClientTest):
3283
 
 
3284
 
    def test_remove_service(self):
3285
 
        env = EnvJujuClient2B7(
3286
 
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
3287
 
        with patch.object(env, 'juju') as mock_juju:
3288
 
            env.remove_service('mondogb')
3289
 
        mock_juju.assert_called_with('remove-service', ('mondogb',))
3290
 
 
3291
 
    def test_deployer(self):
3292
 
        client = EnvJujuClient2B8(JujuData('foo', {'type': 'local'}),
3293
 
                                  '1.23-series-arch', None)
3294
 
        with patch.object(EnvJujuClient, 'juju') as mock:
3295
 
            client.deployer('bundle:~juju-qa/some-bundle')
3296
 
        mock.assert_called_with(
3297
 
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
3298
 
                         '10', '--timeout', '3600', '--config',
3299
 
                         'bundle:~juju-qa/some-bundle'),
3300
 
            True, include_e=False
3301
 
        )
3302
 
 
3303
 
    def test_deployer_with_bundle_name(self):
3304
 
        client = EnvJujuClient2B8(JujuData('foo', {'type': 'local'}),
3305
 
                                  '2.0.0-series-arch', None)
3306
 
        with patch.object(EnvJujuClient, 'juju') as mock:
3307
 
            client.deployer('bundle:~juju-qa/some-bundle', 'name')
3308
 
        mock.assert_called_with(
3309
 
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
3310
 
                         '10', '--timeout', '3600', '--config',
3311
 
                         'bundle:~juju-qa/some-bundle', 'name'),
3312
 
            True, include_e=False
3313
 
        )
3314
 
 
3315
 
 
3316
 
class TestEnvJujuClient2B9(ClientTest):
3317
 
 
3318
 
    def test_get_model_uuid_returns_uuid(self):
3319
 
        model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194'
3320
 
        yaml_string = dedent("""\
3321
 
        foo:
3322
 
          name: foo
3323
 
          model-uuid: {uuid}
3324
 
          controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202
3325
 
          owner: admin@local
3326
 
          cloud: lxd
3327
 
          region: localhost
3328
 
          type: lxd
3329
 
          life: alive
3330
 
          status:
3331
 
            current: available
3332
 
            since: 1 minute ago
3333
 
          users:
3334
 
            admin@local:
3335
 
              display-name: admin
3336
 
              access: admin
3337
 
              last-connection: just now
3338
 
            """.format(uuid=model_uuid))
3339
 
        client = EnvJujuClient2B9(JujuData('foo'), None, None)
3340
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
3341
 
            m_get_juju_output.return_value = yaml_string
3342
 
            self.assertEqual(
3343
 
                client.get_model_uuid(),
3344
 
                model_uuid
3345
 
            )
3346
 
            m_get_juju_output.assert_called_once_with(
3347
 
                'show-model', '--format', 'yaml')
3348
 
 
3349
 
    def test_add_user_perms(self):
3350
 
        fake_client = fake_juju_client(cls=EnvJujuClient2B9)
3351
 
        username = 'fakeuser'
3352
 
        model = 'foo'
3353
 
        permissions = 'write'
3354
 
        output = get_user_register_command_info(username)
3355
 
 
3356
 
        def _get_expected_args(model=None, permissions=None):
3357
 
            return [
3358
 
                username,
3359
 
                '--models', model or fake_client.env.environment,
3360
 
                '--acl', permissions or 'read',
3361
 
                '-c', fake_client.env.controller.name]
3362
 
 
3363
 
        # Ensure add_user_perms returns expected value.
3364
 
        self.assertEqual(
3365
 
            fake_client.add_user_perms(username),
3366
 
            get_user_register_token(username))
3367
 
 
3368
 
        with patch.object(fake_client, 'get_juju_output',
3369
 
                          return_value=output) as get_output:
3370
 
            # Check using default model and permissions
3371
 
            fake_client.add_user_perms(username)
3372
 
            expected_args = _get_expected_args()
3373
 
            get_output.assert_called_with(
3374
 
                'add-user', *expected_args, include_e=False)
3375
 
 
3376
 
            # Check explicit model & default permissions
3377
 
            fake_client.add_user_perms(username, model)
3378
 
            expected_args = _get_expected_args(model)
3379
 
            get_output.assert_called_with(
3380
 
                'add-user', *expected_args, include_e=False)
3381
 
 
3382
 
            # Check explicit model & permissions
3383
 
            fake_client.add_user_perms(username, model, permissions)
3384
 
            expected_args = _get_expected_args(model, permissions)
3385
 
            get_output.assert_called_with(
3386
 
                'add-user', *expected_args, include_e=False)
3387
 
 
3388
 
            # Check default model & explicit permissions
3389
 
            fake_client.add_user_perms(username, permissions=permissions)
3390
 
            expected_args = _get_expected_args(permissions=permissions)
3391
 
            get_output.assert_called_with(
3392
 
                'add-user', *expected_args, include_e=False)
3393
 
 
3394
 
    def test_set_config(self):
3395
 
        client = EnvJujuClient2B9(JujuData('bar', {}), None, '/foo')
3396
 
        with patch.object(client, 'juju') as juju_mock:
3397
 
            client.set_config('foo', {'bar': 'baz'})
3398
 
        juju_mock.assert_called_once_with('set-config', ('foo', 'bar=baz'))
3399
 
 
3400
 
    def test_get_config(self):
3401
 
        def output(*args, **kwargs):
3402
 
            return yaml.safe_dump({
3403
 
                'charm': 'foo',
3404
 
                'service': 'foo',
3405
 
                'settings': {
3406
 
                    'dir': {
3407
 
                        'default': 'true',
3408
 
                        'description': 'bla bla',
3409
 
                        'type': 'string',
3410
 
                        'value': '/tmp/charm-dir',
3411
 
                    }
3412
 
                }
3413
 
            })
3414
 
        expected = yaml.safe_load(output())
3415
 
        client = EnvJujuClient2B9(JujuData('bar', {}), None, '/foo')
3416
 
        with patch.object(client, 'get_juju_output',
3417
 
                          side_effect=output) as gjo_mock:
3418
 
            results = client.get_config('foo')
3419
 
        self.assertEqual(expected, results)
3420
 
        gjo_mock.assert_called_once_with('get-config', 'foo')
3421
 
 
3422
 
    def test_get_model_config(self):
3423
 
        env = JujuData('foo', None)
3424
 
        fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
3425
 
        client = EnvJujuClient2B9(env, None, 'juju')
3426
 
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
3427
 
            result = client.get_model_config()
3428
 
        assert_juju_call(
3429
 
            self, po_mock, client, (
3430
 
                'juju', '--show-log',
3431
 
                'get-model-config',
3432
 
                '-m', 'foo:foo',
3433
 
                '--format', 'yaml'))
3434
 
        self.assertEqual({'bar': 'baz'}, result)
3435
 
 
3436
 
    def test_get_env_option(self):
3437
 
        env = JujuData('foo', None)
3438
 
        fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
3439
 
        client = EnvJujuClient2B9(env, None, 'juju')
3440
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
3441
 
            result = client.get_env_option('tools-metadata-url')
3442
 
        self.assertEqual(
3443
 
            mock.call_args[0][0],
3444
 
            ('juju', '--show-log', 'get-model-config', '-m', 'foo:foo',
3445
 
             'tools-metadata-url'))
3446
 
        self.assertEqual('https://example.org/juju/tools', result)
3447
 
 
3448
 
    def test_set_env_option(self):
3449
 
        env = JujuData('foo')
3450
 
        client = EnvJujuClient2B9(env, None, 'juju')
3451
 
        with patch('subprocess.check_call') as mock:
3452
 
            client.set_env_option(
3453
 
                'tools-metadata-url', 'https://example.org/juju/tools')
3454
 
        environ = dict(os.environ)
3455
 
        environ['JUJU_HOME'] = client.env.juju_home
3456
 
        mock.assert_called_with(
3457
 
            ('juju', '--show-log', 'set-model-config', '-m', 'foo:foo',
3458
 
             'tools-metadata-url=https://example.org/juju/tools'))
3459
 
 
3460
 
    def test_unset_env_option(self):
3461
 
        env = JujuData('foo')
3462
 
        client = EnvJujuClient2B9(env, None, 'juju')
3463
 
        with patch('subprocess.check_call') as mock:
3464
 
            client.unset_env_option('tools-metadata-url')
3465
 
        environ = dict(os.environ)
3466
 
        environ['JUJU_HOME'] = client.env.juju_home
3467
 
        mock.assert_called_with(
3468
 
            ('juju', '--show-log', 'unset-model-config', '-m', 'foo:foo',
3469
 
             'tools-metadata-url'))
3470
 
 
3471
 
    def test_list_disabled_commands(self):
3472
 
        client = EnvJujuClient2B9(JujuData('foo'), None, None)
3473
 
        with patch.object(client, 'get_juju_output', autospec=True,
3474
 
                          return_value=dedent("""\
3475
 
             - command-set: destroy-model
3476
 
               message: Lock Models
3477
 
             - command-set: remove-object""")) as mock:
3478
 
            output = client.list_disabled_commands()
3479
 
        self.assertEqual([{'command-set': 'destroy-model',
3480
 
                           'message': 'Lock Models'},
3481
 
                          {'command-set': 'remove-object'}], output)
3482
 
        mock.assert_called_once_with('block list',
3483
 
                                     '--format', 'yaml')
3484
 
 
3485
 
    def test_disable_command(self):
3486
 
        client = EnvJujuClient2B9(JujuData('foo'), None, None)
3487
 
        with patch.object(client, 'juju', autospec=True) as mock:
3488
 
            client.disable_command(('all', 'message'))
3489
 
        mock.assert_called_once_with('block', ('all', 'message'))
3490
 
 
3491
 
    def test_enable_command(self):
3492
 
        client = EnvJujuClient2B9(JujuData('foo'), None, None)
3493
 
        with patch.object(client, 'juju', autospec=True) as mock:
3494
 
            client.enable_command('all')
3495
 
        mock.assert_called_once_with('unblock', 'all')
3496
 
 
3497
 
 
3498
 
class TestEnvJujuClient2B7(ClientTest):
3499
 
 
3500
 
    def test_get_controller_model_name(self):
3501
 
        models = {
3502
 
            'models': [
3503
 
                {'name': 'admin', 'model-uuid': 'aaaa'},
3504
 
                {'name': 'bar', 'model-uuid': 'bbbb'}],
3505
 
            'current-model': 'bar'
3506
 
        }
3507
 
        client = EnvJujuClient2B7(JujuData('foo'), None, None)
3508
 
        with patch.object(client, 'get_models',
3509
 
                          return_value=models) as gm_mock:
3510
 
            controller_name = client.get_controller_model_name()
3511
 
        self.assertEqual(0, gm_mock.call_count)
3512
 
        self.assertEqual('admin', controller_name)
3513
 
 
3514
 
    def test_get_controller_model_name_without_controller(self):
3515
 
        models = {
3516
 
            'models': [
3517
 
                {'name': 'bar', 'model-uuid': 'aaaa'},
3518
 
                {'name': 'baz', 'model-uuid': 'bbbb'}],
3519
 
            'current-model': 'bar'
3520
 
        }
3521
 
        client = EnvJujuClient2B7(JujuData('foo'), None, None)
3522
 
        with patch.object(client, 'get_models', return_value=models):
3523
 
            controller_name = client.get_controller_model_name()
3524
 
        self.assertEqual('admin', controller_name)
3525
 
 
3526
 
    def test_get_controller_model_name_no_models(self):
3527
 
        client = EnvJujuClient2B7(JujuData('foo'), None, None)
3528
 
        with patch.object(client, 'get_models', return_value={}):
3529
 
            controller_name = client.get_controller_model_name()
3530
 
        self.assertEqual('admin', controller_name)
3531
 
 
3532
 
    def test_get_controller_client(self):
3533
 
        client = EnvJujuClient2B7(
3534
 
            JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)
3535
 
        controller_client = client.get_controller_client()
3536
 
        controller_env = controller_client.env
3537
 
        self.assertEqual('admin', controller_env.environment)
3538
 
        self.assertEqual({'bar': 'baz', 'name': 'admin'},
3539
 
                         controller_env.config)
3540
 
 
3541
 
 
3542
 
class TestEnvJujuClient2B3(ClientTest):
3543
 
 
3544
 
    def test_add_model_hypenated_controller(self):
3545
 
        self.do_add_model(
3546
 
            'kill-controller', 'create-model', ('-c', 'foo'))
3547
 
 
3548
 
    def do_add_model(self, jes_command, create_cmd, controller_option):
3549
 
        controller_client = EnvJujuClient2B3(JujuData('foo'), None, None)
3550
 
        model_data = JujuData('bar', {'type': 'foo'})
3551
 
        client = EnvJujuClient2B3(model_data, None, None)
3552
 
        with patch.object(client, 'get_jes_command',
3553
 
                          return_value=jes_command):
3554
 
                with patch.object(controller_client, 'juju') as ccj_mock:
3555
 
                    with observable_temp_file() as config_file:
3556
 
                        controller_client.add_model(model_data)
3557
 
        ccj_mock.assert_called_once_with(
3558
 
            create_cmd, controller_option + (
3559
 
                'bar', '--config', config_file.name), include_e=False)
3560
 
 
3561
 
 
3562
 
class TestEnvJujuClient2B2(ClientTest):
3563
 
 
3564
 
    def test_get_bootstrap_args_bootstrap_series(self):
3565
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3566
 
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3567
 
        args = client.get_bootstrap_args(upload_tools=True,
3568
 
                                         config_filename='config',
3569
 
                                         bootstrap_series='angsty')
3570
 
        self.assertEqual(args, (
3571
 
            '--upload-tools', '--constraints', 'mem=2G', 'foo', 'bar/baz',
3572
 
            '--config', 'config', '--bootstrap-series', 'angsty'))
3573
 
 
3574
 
    def test_get_bootstrap_args_reject_new_args(self):
3575
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3576
 
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3577
 
        base_args = {'upload_tools': True,
3578
 
                     'config_filename': 'config',
3579
 
                     'bootstrap_series': 'angsty'}
3580
 
        with self.assertRaises(ValueError):
3581
 
            client.get_bootstrap_args(auto_upgrade=True, **base_args)
3582
 
        with self.assertRaises(ValueError):
3583
 
            client.get_bootstrap_args(metadata_source='/foo', **base_args)
3584
 
        with self.assertRaises(ValueError):
3585
 
            client.get_bootstrap_args(to='cur', **base_args)
3586
 
        with self.assertRaises(ValueError):
3587
 
            client.get_bootstrap_args(agent_version='1.0.0', **base_args)
3588
 
 
3589
 
    def test_bootstrap_upload_tools(self):
3590
 
        env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
3591
 
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3592
 
        with patch.object(client.env, 'needs_sudo', lambda: True):
3593
 
            with observable_temp_file() as config_file:
3594
 
                with patch.object(client, 'juju') as mock:
3595
 
                    client.bootstrap(upload_tools=True)
3596
 
            mock.assert_called_with(
3597
 
                'bootstrap', (
3598
 
                    '--upload-tools', '--constraints', 'mem=2G', 'foo',
3599
 
                    'foo/baz', '--config', config_file.name), include_e=False)
3600
 
 
3601
 
    def test_bootstrap_maas(self):
3602
 
        env = JujuData('maas', {'type': 'foo', 'region': 'asdf'})
3603
 
        with patch.object(EnvJujuClient, 'juju') as mock:
3604
 
            client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3605
 
            with patch.object(client.env, 'maas', lambda: True):
3606
 
                with observable_temp_file() as config_file:
3607
 
                    client.bootstrap()
3608
 
            mock.assert_called_with(
3609
 
                'bootstrap', (
3610
 
                    '--constraints', 'mem=2G', 'maas', 'foo/asdf',
3611
 
                    '--config', config_file.name, '--agent-version', '2.0'),
3612
 
                include_e=False)
3613
 
 
3614
 
    def test_bootstrap_joyent(self):
3615
 
        env = JujuData('joyent', {
3616
 
            'type': 'joyent', 'sdc-url': 'https://foo.api.joyentcloud.com'})
3617
 
        with patch.object(EnvJujuClient, 'juju', autospec=True) as mock:
3618
 
            client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3619
 
            with patch.object(client.env, 'joyent', lambda: True):
3620
 
                with observable_temp_file() as config_file:
3621
 
                    client.bootstrap()
3622
 
            mock.assert_called_once_with(
3623
 
                client, 'bootstrap', (
3624
 
                    '--constraints', 'mem=2G cpu-cores=1', 'joyent',
3625
 
                    'joyent/foo', '--config', config_file.name,
3626
 
                    '--agent-version', '2.0'), include_e=False)
3627
 
 
3628
 
    def test_bootstrap_async_upload_tools(self):
3629
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3630
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
3631
 
            client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3632
 
            with observable_temp_file() as config_file:
3633
 
                with client.bootstrap_async(upload_tools=True):
3634
 
                    mock.assert_called_with(
3635
 
                        client, 'bootstrap', (
3636
 
                            '--upload-tools', '--constraints', 'mem=2G',
3637
 
                            'foo', 'bar/baz', '--config', config_file.name),
3638
 
                        include_e=False)
3639
 
 
3640
 
    def test_bootstrap_async(self):
3641
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3642
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
3643
 
            client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3644
 
            client.env.juju_home = 'foo'
3645
 
            with observable_temp_file() as config_file:
3646
 
                with client.bootstrap_async():
3647
 
                    mock.assert_called_once_with(
3648
 
                        client, 'bootstrap', (
3649
 
                            '--constraints', 'mem=2G', 'foo', 'bar/baz',
3650
 
                            '--config', config_file.name,
3651
 
                            '--agent-version', '2.0'), include_e=False)
3652
 
 
3653
 
    def test_bootstrap_args(self):
3654
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3655
 
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3656
 
        with patch.object(client, 'juju') as mock:
3657
 
            with observable_temp_file() as config_file:
3658
 
                client.bootstrap(bootstrap_series='angsty')
3659
 
        mock.assert_called_with(
3660
 
            'bootstrap', (
3661
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
3662
 
                '--config', config_file.name,
3663
 
                '--agent-version', '2.0',
3664
 
                '--bootstrap-series', 'angsty'), include_e=False)
3665
 
 
3666
 
    def test_bootstrap(self):
3667
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3668
 
        with observable_temp_file() as config_file:
3669
 
            with patch.object(EnvJujuClient, 'juju') as mock:
3670
 
                client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3671
 
                client.bootstrap()
3672
 
                mock.assert_called_with(
3673
 
                    'bootstrap', ('--constraints', 'mem=2G',
3674
 
                                  'foo', 'bar/baz',
3675
 
                                  '--config', config_file.name,
3676
 
                                  '--agent-version', '2.0'), include_e=False)
3677
 
                config_file.seek(0)
3678
 
                config = yaml.safe_load(config_file)
3679
 
        self.assertEqual({'test-mode': True}, config)
3680
 
 
3681
 
    def test_get_controller_model_name(self):
3682
 
        models = {
3683
 
            'models': [
3684
 
                {'name': 'admin', 'model-uuid': 'aaaa'},
3685
 
                {'name': 'bar', 'model-uuid': 'bbbb'}],
3686
 
            'current-model': 'bar'
3687
 
        }
3688
 
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3689
 
        with patch.object(client, 'get_models',
3690
 
                          return_value=models) as gm_mock:
3691
 
            controller_name = client.get_controller_model_name()
3692
 
        gm_mock.assert_called_once_with()
3693
 
        self.assertEqual('admin', controller_name)
3694
 
 
3695
 
    def test_get_controller_model_name_without_controller(self):
3696
 
        models = {
3697
 
            'models': [
3698
 
                {'name': 'bar', 'model-uuid': 'aaaa'},
3699
 
                {'name': 'baz', 'model-uuid': 'bbbb'}],
3700
 
            'current-model': 'bar'
3701
 
        }
3702
 
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3703
 
        with patch.object(client, 'get_models', return_value=models):
3704
 
            controller_name = client.get_controller_model_name()
3705
 
        self.assertEqual('foo', controller_name)
3706
 
 
3707
 
    def test_get_controller_model_name_no_models(self):
3708
 
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3709
 
        with patch.object(client, 'get_models', return_value={}):
3710
 
            controller_name = client.get_controller_model_name()
3711
 
        self.assertEqual('foo', controller_name)
3712
 
 
3713
 
 
3714
 
class TestEnvJujuClient2A2(TestCase):
3715
 
 
3716
 
    def test_raise_on_juju_data(self):
3717
 
        env = JujuData('foo', {'type': 'bar'}, 'baz')
3718
 
        with self.assertRaisesRegexp(
3719
 
                IncompatibleConfigClass,
3720
 
                'JujuData cannot be used with EnvJujuClient2A2'):
3721
 
            EnvJujuClient2A2(env, '1.25', 'full_path')
3722
 
 
3723
 
    def test__shell_environ_juju_home(self):
3724
 
        client = EnvJujuClient2A2(
3725
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.25-foobar', 'path',
3726
 
            'asdf')
3727
 
        with patch.dict(os.environ, {'PATH': ''}):
3728
 
            env = client._shell_environ()
3729
 
        # For transition, supply both.
3730
 
        self.assertEqual(env['JUJU_HOME'], 'asdf')
3731
 
        self.assertEqual(env['JUJU_DATA'], 'asdf')
3732
 
 
3733
 
    def test_get_bootstrap_args_bootstrap_series(self):
3734
 
        env = SimpleEnvironment('foo', {})
3735
 
        client = EnvJujuClient2A2(env, '2.0-zeta1', 'path', 'home')
3736
 
        args = client.get_bootstrap_args(upload_tools=True,
3737
 
                                         bootstrap_series='angsty')
3738
 
        self.assertEqual(args, (
3739
 
            '--upload-tools', '--constraints', 'mem=2G',
3740
 
            '--agent-version', '2.0', '--bootstrap-series', 'angsty'))
3741
 
 
3742
 
 
3743
 
class TestEnvJujuClient1X(ClientTest):
3744
 
 
3745
 
    def test_no_duplicate_env(self):
3746
 
        env = SimpleEnvironment('foo', {})
3747
 
        client = EnvJujuClient1X(env, '1.25', 'full_path')
3748
 
        self.assertIs(env, client.env)
3749
 
 
3750
 
    def test_get_version(self):
3751
 
        value = ' 5.6 \n'
3752
 
        with patch('subprocess.check_output', return_value=value) as vsn:
3753
 
            version = EnvJujuClient1X.get_version()
3754
 
        self.assertEqual('5.6', version)
3755
 
        vsn.assert_called_with(('juju', '--version'))
3756
 
 
3757
 
    def test_get_version_path(self):
3758
 
        with patch('subprocess.check_output', return_value=' 4.3') as vsn:
3759
 
            EnvJujuClient1X.get_version('foo/bar/baz')
3760
 
        vsn.assert_called_once_with(('foo/bar/baz', '--version'))
3761
 
 
3762
 
    def test_get_matching_agent_version(self):
3763
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
3764
 
                                 '1.23-series-arch', None)
3765
 
        self.assertEqual('1.23.1', client.get_matching_agent_version())
3766
 
        self.assertEqual('1.23', client.get_matching_agent_version(
3767
 
                         no_build=True))
3768
 
        client = client.clone(version='1.20-beta1-series-arch')
3769
 
        self.assertEqual('1.20-beta1.1', client.get_matching_agent_version())
3770
 
 
3771
 
    def test_upgrade_juju_nonlocal(self):
3772
 
        client = EnvJujuClient1X(
3773
 
            SimpleEnvironment('foo', {'type': 'nonlocal'}), '1.234-76', None)
3774
 
        with patch.object(client, 'juju') as juju_mock:
3775
 
            client.upgrade_juju()
3776
 
        juju_mock.assert_called_with(
3777
 
            'upgrade-juju', ('--version', '1.234'))
3778
 
 
3779
 
    def test_upgrade_juju_local(self):
3780
 
        client = EnvJujuClient1X(
3781
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
3782
 
        with patch.object(client, 'juju') as juju_mock:
3783
 
            client.upgrade_juju()
3784
 
        juju_mock.assert_called_with(
3785
 
            'upgrade-juju', ('--version', '1.234', '--upload-tools',))
3786
 
 
3787
 
    def test_upgrade_juju_no_force_version(self):
3788
 
        client = EnvJujuClient1X(
3789
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
3790
 
        with patch.object(client, 'juju') as juju_mock:
3791
 
            client.upgrade_juju(force_version=False)
3792
 
        juju_mock.assert_called_with(
3793
 
            'upgrade-juju', ('--upload-tools',))
3794
 
 
3795
 
    def test_upgrade_mongo_exception(self):
3796
 
        client = EnvJujuClient1X(
3797
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
3798
 
        with self.assertRaises(UpgradeMongoNotSupported):
3799
 
            client.upgrade_mongo()
3800
 
 
3801
 
    def test_get_cache_path(self):
3802
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', juju_home='/foo/'),
3803
 
                                 '1.27', 'full/path', debug=True)
3804
 
        self.assertEqual('/foo/environments/cache.yaml',
3805
 
                         client.get_cache_path())
3806
 
 
3807
 
    def test_full_args(self):
3808
 
        env = SimpleEnvironment('foo')
3809
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3810
 
        full = client._full_args('bar', False, ('baz', 'qux'))
3811
 
        self.assertEqual(('bin', '--show-log', 'bar', '-e', 'foo', 'baz',
3812
 
                          'qux'), full)
3813
 
        full = client._full_args('bar', True, ('baz', 'qux'))
3814
 
        self.assertEqual((
3815
 
            'bin', '--show-log', 'bar', '-e', 'foo',
 
153
    def test_full_args(self):
 
154
        env = Environment('foo', '')
 
155
        client = EnvJujuClient(env, None, 'my/juju/bin')
 
156
        full = client._full_args('bar', False, ('baz', 'qux'))
 
157
        self.assertEqual(('juju', '--show-log', 'bar', '-e', 'foo', 'baz',
 
158
                          'qux'), full)
 
159
        full = client._full_args('bar', True, ('baz', 'qux'))
 
160
        self.assertEqual((
 
161
            'juju', '--show-log', 'bar', '-e', 'foo',
3816
162
            'baz', 'qux'), full)
3817
163
        client.env = None
3818
164
        full = client._full_args('bar', False, ('baz', 'qux'))
3819
 
        self.assertEqual(('bin', '--show-log', 'bar', 'baz', 'qux'), full)
 
165
        self.assertEqual(('juju', '--show-log', 'bar', 'baz', 'qux'), full)
3820
166
 
3821
167
    def test_full_args_debug(self):
3822
 
        env = SimpleEnvironment('foo')
3823
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin', debug=True)
 
168
        env = Environment('foo', '')
 
169
        client = EnvJujuClient(env, None, 'my/juju/bin')
 
170
        client.debug = True
3824
171
        full = client._full_args('bar', False, ('baz', 'qux'))
3825
172
        self.assertEqual((
3826
 
            'bin', '--debug', 'bar', '-e', 'foo', 'baz', 'qux'), full)
3827
 
 
3828
 
    def test_full_args_controller(self):
3829
 
        env = SimpleEnvironment('foo')
3830
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3831
 
        full = client._full_args('bar', False, ('baz', 'qux'), controller=True)
3832
 
        self.assertEqual((
3833
 
            'bin', '--show-log', 'bar', '-e', 'foo', 'baz', 'qux'), full)
3834
 
 
3835
 
    def test_full_args_action(self):
3836
 
        env = SimpleEnvironment('foo')
3837
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3838
 
        full = client._full_args('action bar', False, ('baz', 'qux'))
3839
 
        self.assertEqual((
3840
 
            'bin', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux'),
3841
 
            full)
 
173
            'juju', '--debug', 'bar', '-e', 'foo', 'baz', 'qux'), full)
 
174
 
 
175
    def test_bootstrap_hpcloud(self):
 
176
        env = Environment('hp', '')
 
177
        with patch.object(env, 'hpcloud', lambda: True):
 
178
            with patch.object(EnvJujuClient, 'juju') as mock:
 
179
                EnvJujuClient(env, None, None).bootstrap()
 
180
            mock.assert_called_with(
 
181
                'bootstrap', ('--constraints', 'mem=2G'), False)
3842
182
 
3843
183
    def test_bootstrap_maas(self):
3844
 
        env = SimpleEnvironment('maas')
3845
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
3846
 
            client = EnvJujuClient1X(env, None, None)
 
184
        env = Environment('maas', '')
 
185
        with patch.object(EnvJujuClient, 'juju') as mock:
 
186
            client = EnvJujuClient(env, None, None)
3847
187
            with patch.object(client.env, 'maas', lambda: True):
3848
188
                client.bootstrap()
3849
189
            mock.assert_called_with(
3850
 
                'bootstrap', ('--constraints', 'mem=2G'), False)
3851
 
 
3852
 
    def test_bootstrap_joyent(self):
3853
 
        env = SimpleEnvironment('joyent')
3854
 
        with patch.object(EnvJujuClient1X, 'juju', autospec=True) as mock:
3855
 
            client = EnvJujuClient1X(env, None, None)
3856
 
            with patch.object(client.env, 'joyent', lambda: True):
3857
 
                client.bootstrap()
3858
 
            mock.assert_called_once_with(
3859
 
                client, 'bootstrap', ('--constraints', 'mem=2G cpu-cores=1'),
3860
 
                False)
 
190
                'bootstrap', ('--constraints', 'mem=2G arch=amd64'), False)
3861
191
 
3862
192
    def test_bootstrap_non_sudo(self):
3863
 
        env = SimpleEnvironment('foo')
3864
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
3865
 
            client = EnvJujuClient1X(env, None, None)
 
193
        env = Environment('foo', '')
 
194
        with patch.object(EnvJujuClient, 'juju') as mock:
 
195
            client = EnvJujuClient(env, None, None)
3866
196
            with patch.object(client.env, 'needs_sudo', lambda: False):
3867
197
                client.bootstrap()
3868
198
            mock.assert_called_with(
3869
199
                'bootstrap', ('--constraints', 'mem=2G'), False)
3870
200
 
3871
201
    def test_bootstrap_sudo(self):
3872
 
        env = SimpleEnvironment('foo')
3873
 
        client = EnvJujuClient1X(env, None, None)
 
202
        env = Environment('foo', '')
 
203
        client = EnvJujuClient(env, None, None)
3874
204
        with patch.object(client.env, 'needs_sudo', lambda: True):
3875
205
            with patch.object(client, 'juju') as mock:
3876
206
                client.bootstrap()
3878
208
                'bootstrap', ('--constraints', 'mem=2G'), True)
3879
209
 
3880
210
    def test_bootstrap_upload_tools(self):
3881
 
        env = SimpleEnvironment('foo')
3882
 
        client = EnvJujuClient1X(env, None, None)
 
211
        env = Environment('foo', '')
 
212
        client = EnvJujuClient(env, None, None)
3883
213
        with patch.object(client.env, 'needs_sudo', lambda: True):
3884
214
            with patch.object(client, 'juju') as mock:
3885
215
                client.bootstrap(upload_tools=True)
3887
217
                'bootstrap', ('--upload-tools', '--constraints', 'mem=2G'),
3888
218
                True)
3889
219
 
3890
 
    def test_bootstrap_args(self):
3891
 
        env = SimpleEnvironment('foo', {})
3892
 
        client = EnvJujuClient1X(env, None, None)
3893
 
        with self.assertRaisesRegexp(
3894
 
                BootstrapMismatch,
3895
 
                '--bootstrap-series angsty does not match default-series:'
3896
 
                ' None'):
3897
 
            client.bootstrap(bootstrap_series='angsty')
3898
 
        env.config.update({
3899
 
            'default-series': 'angsty',
3900
 
            })
3901
 
        with patch.object(client, 'juju') as mock:
3902
 
            client.bootstrap(bootstrap_series='angsty')
3903
 
        mock.assert_called_with(
3904
 
            'bootstrap', ('--constraints', 'mem=2G'),
3905
 
            False)
3906
 
 
3907
 
    def test_bootstrap_async(self):
3908
 
        env = SimpleEnvironment('foo')
3909
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
3910
 
            client = EnvJujuClient1X(env, None, None)
3911
 
            client.env.juju_home = 'foo'
3912
 
            with client.bootstrap_async():
3913
 
                mock.assert_called_once_with(
3914
 
                    client, 'bootstrap', ('--constraints', 'mem=2G'))
3915
 
 
3916
 
    def test_bootstrap_async_upload_tools(self):
3917
 
        env = SimpleEnvironment('foo')
3918
 
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
3919
 
            client = EnvJujuClient1X(env, None, None)
3920
 
            with client.bootstrap_async(upload_tools=True):
3921
 
                mock.assert_called_with(
3922
 
                    client, 'bootstrap', ('--upload-tools', '--constraints',
3923
 
                                          'mem=2G'))
3924
 
 
3925
 
    def test_get_bootstrap_args_bootstrap_series(self):
3926
 
        env = SimpleEnvironment('foo', {})
3927
 
        client = EnvJujuClient1X(env, None, None)
3928
 
        with self.assertRaisesRegexp(
3929
 
                BootstrapMismatch,
3930
 
                '--bootstrap-series angsty does not match default-series:'
3931
 
                ' None'):
3932
 
            client.get_bootstrap_args(upload_tools=True,
3933
 
                                      bootstrap_series='angsty')
3934
 
        env.config['default-series'] = 'angsty'
3935
 
        args = client.get_bootstrap_args(upload_tools=True,
3936
 
                                         bootstrap_series='angsty')
3937
 
        self.assertEqual(args, ('--upload-tools', '--constraints', 'mem=2G'))
3938
 
 
3939
 
    def test_create_environment_system(self):
3940
 
        self.do_create_environment(
3941
 
            'system', 'system create-environment', ('-s', 'foo'))
3942
 
 
3943
 
    def test_create_environment_controller(self):
3944
 
        self.do_create_environment(
3945
 
            'controller', 'controller create-environment', ('-c', 'foo'))
3946
 
 
3947
 
    def test_create_environment_hypenated_controller(self):
3948
 
        self.do_create_environment(
3949
 
            'kill-controller', 'create-environment', ('-c', 'foo'))
3950
 
 
3951
 
    def do_create_environment(self, jes_command, create_cmd,
3952
 
                              controller_option):
3953
 
        controller_client = EnvJujuClient1X(SimpleEnvironment('foo'), '1.26.1',
3954
 
                                            None)
3955
 
        model_env = SimpleEnvironment('bar', {'type': 'foo'})
3956
 
        with patch.object(controller_client, 'get_jes_command',
3957
 
                          return_value=jes_command):
3958
 
            with patch.object(controller_client, 'juju') as juju_mock:
3959
 
                with observable_temp_file() as config_file:
3960
 
                    controller_client.add_model(model_env)
3961
 
        juju_mock.assert_called_once_with(
3962
 
            create_cmd, controller_option + (
3963
 
                'bar', '--config', config_file.name), include_e=False)
3964
 
 
3965
220
    def test_destroy_environment_non_sudo(self):
3966
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
3967
 
        client = EnvJujuClient1X(env, None, None)
 
221
        env = Environment('foo', '')
 
222
        client = EnvJujuClient(env, None, None)
3968
223
        with patch.object(client.env, 'needs_sudo', lambda: False):
3969
224
            with patch.object(client, 'juju') as mock:
3970
225
                client.destroy_environment()
3971
226
            mock.assert_called_with(
3972
227
                'destroy-environment', ('foo', '--force', '-y'),
3973
 
                False, check=False, include_e=False, timeout=600)
 
228
                False, check=False, include_e=False, timeout=600.0)
3974
229
 
3975
230
    def test_destroy_environment_sudo(self):
3976
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
3977
 
        client = EnvJujuClient1X(env, None, None)
 
231
        env = Environment('foo', '')
 
232
        client = EnvJujuClient(env, None, None)
3978
233
        with patch.object(client.env, 'needs_sudo', lambda: True):
3979
234
            with patch.object(client, 'juju') as mock:
3980
235
                client.destroy_environment()
3981
236
            mock.assert_called_with(
3982
237
                'destroy-environment', ('foo', '--force', '-y'),
3983
 
                True, check=False, include_e=False, timeout=600)
 
238
                True, check=False, include_e=False, timeout=600.0)
3984
239
 
3985
240
    def test_destroy_environment_no_force(self):
3986
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
3987
 
        client = EnvJujuClient1X(env, None, None)
3988
 
        with patch.object(client, 'juju') as mock:
3989
 
            client.destroy_environment(force=False)
3990
 
            mock.assert_called_with(
3991
 
                'destroy-environment', ('foo', '-y'),
3992
 
                False, check=False, include_e=False, timeout=600)
3993
 
 
3994
 
    def test_destroy_environment_azure(self):
3995
 
        env = SimpleEnvironment('foo', {'type': 'azure'})
3996
 
        client = EnvJujuClient1X(env, None, None)
3997
 
        with patch.object(client, 'juju') as mock:
3998
 
            client.destroy_environment(force=False)
3999
 
            mock.assert_called_with(
4000
 
                'destroy-environment', ('foo', '-y'),
4001
 
                False, check=False, include_e=False, timeout=1800)
 
241
        env = Environment('foo', '')
 
242
        client = EnvJujuClient(env, None, None)
 
243
        with patch.object(client, 'juju') as mock:
 
244
            client.destroy_environment(force=False)
 
245
            mock.assert_called_with(
 
246
                'destroy-environment', ('foo', '-y'),
 
247
                False, check=False, include_e=False, timeout=600.0)
4002
248
 
4003
249
    def test_destroy_environment_delete_jenv(self):
4004
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
4005
 
        client = EnvJujuClient1X(env, None, None)
 
250
        env = Environment('foo', '')
 
251
        client = EnvJujuClient(env, None, None)
4006
252
        with patch.object(client, 'juju'):
4007
 
            with temp_env({}) as juju_home:
4008
 
                client.env.juju_home = juju_home
 
253
            with _temp_env({}) as juju_home:
4009
254
                jenv_path = get_jenv_path(juju_home, 'foo')
4010
255
                os.makedirs(os.path.dirname(jenv_path))
4011
256
                open(jenv_path, 'w')
4013
258
                client.destroy_environment(delete_jenv=True)
4014
259
                self.assertFalse(os.path.exists(jenv_path))
4015
260
 
4016
 
    def test_destroy_model(self):
4017
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
4018
 
        client = EnvJujuClient1X(env, None, None)
4019
 
        with patch.object(client, 'juju') as mock:
4020
 
            client.destroy_model()
4021
 
        mock.assert_called_with(
4022
 
            'destroy-environment', ('foo', '-y'),
4023
 
            False, check=False, include_e=False, timeout=600)
4024
 
 
4025
 
    def test_kill_controller_system(self):
4026
 
        self.do_kill_controller('system', 'system kill')
4027
 
 
4028
 
    def test_kill_controller_controller(self):
4029
 
        self.do_kill_controller('controller', 'controller kill')
4030
 
 
4031
 
    def test_kill_controller_hyphenated(self):
4032
 
        self.do_kill_controller('kill-controller', 'kill-controller')
4033
 
 
4034
 
    def do_kill_controller(self, jes_command, kill_command):
4035
 
        client = EnvJujuClient1X(
4036
 
            SimpleEnvironment('foo', {'type': 'gce'}), None, None)
4037
 
        with patch.object(client, 'get_jes_command',
4038
 
                          return_value=jes_command):
4039
 
            with patch.object(client, 'juju') as juju_mock:
4040
 
                client.kill_controller()
4041
 
        juju_mock.assert_called_once_with(
4042
 
            kill_command, ('foo', '-y'), check=False, include_e=False,
4043
 
            timeout=600)
4044
 
 
4045
261
    def test_get_juju_output(self):
4046
 
        env = SimpleEnvironment('foo')
4047
 
        client = EnvJujuClient1X(env, None, 'juju')
4048
 
        fake_popen = FakePopen('asdf', None, 0)
4049
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
 
262
        env = Environment('foo', '')
 
263
        asdf = lambda x, stderr, env: 'asdf'
 
264
        client = EnvJujuClient(env, None, None)
 
265
        with patch('subprocess.check_output', side_effect=asdf) as mock:
4050
266
            result = client.get_juju_output('bar')
4051
267
        self.assertEqual('asdf', result)
4052
268
        self.assertEqual((('juju', '--show-log', 'bar', '-e', 'foo'),),
4053
269
                         mock.call_args[0])
4054
270
 
4055
271
    def test_get_juju_output_accepts_varargs(self):
4056
 
        env = SimpleEnvironment('foo')
4057
 
        fake_popen = FakePopen('asdf', None, 0)
4058
 
        client = EnvJujuClient1X(env, None, 'juju')
4059
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
 
272
        env = Environment('foo', '')
 
273
        asdf = lambda x, stderr, env: 'asdf'
 
274
        client = EnvJujuClient(env, None, None)
 
275
        with patch('subprocess.check_output', side_effect=asdf) as mock:
4060
276
            result = client.get_juju_output('bar', 'baz', '--qux')
4061
277
        self.assertEqual('asdf', result)
4062
278
        self.assertEqual((('juju', '--show-log', 'bar', '-e', 'foo', 'baz',
4063
279
                           '--qux'),), mock.call_args[0])
4064
280
 
4065
281
    def test_get_juju_output_stderr(self):
4066
 
        env = SimpleEnvironment('foo')
4067
 
        fake_popen = FakePopen('Hello', 'Error!', 1)
4068
 
        client = EnvJujuClient1X(env, None, 'juju')
 
282
        def raise_without_stderr(args, stderr, env):
 
283
            stderr.write('Hello!')
 
284
            raise subprocess.CalledProcessError('a', 'b')
 
285
        env = Environment('foo', '')
 
286
        client = EnvJujuClient(env, None, None)
4069
287
        with self.assertRaises(subprocess.CalledProcessError) as exc:
4070
 
            with patch('subprocess.Popen', return_value=fake_popen):
 
288
            with patch('subprocess.check_output', raise_without_stderr):
4071
289
                client.get_juju_output('bar')
4072
 
        self.assertEqual(exc.exception.output, 'Hello')
4073
 
        self.assertEqual(exc.exception.stderr, 'Error!')
4074
 
 
4075
 
    def test_get_juju_output_full_cmd(self):
4076
 
        env = SimpleEnvironment('foo')
4077
 
        fake_popen = FakePopen(None, 'Hello!', 1)
4078
 
        client = EnvJujuClient1X(env, None, 'juju')
4079
 
        with self.assertRaises(subprocess.CalledProcessError) as exc:
4080
 
            with patch('subprocess.Popen', return_value=fake_popen):
4081
 
                client.get_juju_output('bar', '--baz', 'qux')
4082
 
        self.assertEqual(
4083
 
            ('juju', '--show-log', 'bar', '-e', 'foo', '--baz', 'qux'),
4084
 
            exc.exception.cmd)
 
290
        self.assertEqual(exc.exception.stderr, 'Hello!')
4085
291
 
4086
292
    def test_get_juju_output_accepts_timeout(self):
4087
 
        env = SimpleEnvironment('foo')
4088
 
        fake_popen = FakePopen('asdf', None, 0)
4089
 
        client = EnvJujuClient1X(env, None, 'juju')
4090
 
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
 
293
        env = Environment('foo', '')
 
294
        client = EnvJujuClient(env, None, None)
 
295
        with patch('subprocess.check_output') as sco_mock:
4091
296
            client.get_juju_output('bar', timeout=5)
4092
297
        self.assertEqual(
4093
 
            po_mock.call_args[0][0],
4094
 
            (sys.executable, get_timeout_path(), '5.00', '--', 'juju',
4095
 
             '--show-log', 'bar', '-e', 'foo'))
4096
 
 
4097
 
    def test__shell_environ_juju_home(self):
4098
 
        client = EnvJujuClient1X(
4099
 
            SimpleEnvironment('baz', {'type': 'ec2'}), '1.25-foobar', 'path',
4100
 
            'asdf')
4101
 
        env = client._shell_environ()
4102
 
        self.assertEqual(env['JUJU_HOME'], 'asdf')
4103
 
        self.assertNotIn('JUJU_DATA', env)
4104
 
 
4105
 
    def test__shell_environ_cloudsigma(self):
4106
 
        client = EnvJujuClient1X(
4107
 
            SimpleEnvironment('baz', {'type': 'cloudsigma'}),
4108
 
            '1.24-foobar', 'path')
4109
 
        env = client._shell_environ()
4110
 
        self.assertEqual(env.get(JUJU_DEV_FEATURE_FLAGS, ''), '')
 
298
            sco_mock.call_args[0][0],
 
299
            ('timeout', '5.00s', 'juju', '--show-log', 'bar', '-e', 'foo'))
4111
300
 
4112
301
    def test_juju_output_supplies_path(self):
4113
 
        env = SimpleEnvironment('foo')
4114
 
        client = EnvJujuClient1X(env, None, '/foobar/bar')
4115
 
 
4116
 
        def check_path(*args, **kwargs):
4117
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
4118
 
            return FakePopen(None, None, 0)
4119
 
        with patch('subprocess.Popen', autospec=True,
4120
 
                   side_effect=check_path):
4121
 
            client.get_juju_output('cmd', 'baz')
 
302
        env = Environment('foo', '')
 
303
        client = EnvJujuClient(env, None, '/foobar/bar')
 
304
        with patch('subprocess.check_output') as sco_mock:
 
305
            client.get_juju_output(env, 'baz')
 
306
        self.assertRegexpMatches(sco_mock.call_args[1]['env']['PATH'],
 
307
                                 r'/foobar\:')
4122
308
 
4123
309
    def test_get_status(self):
4124
 
        output_text = dedent("""\
 
310
        output_text = yield dedent("""\
4125
311
                - a
4126
312
                - b
4127
313
                - c
4128
314
                """)
4129
 
        env = SimpleEnvironment('foo')
4130
 
        client = EnvJujuClient1X(env, None, None)
 
315
        env = Environment('foo', '')
 
316
        client = EnvJujuClient(env, None, None)
4131
317
        with patch.object(client, 'get_juju_output',
4132
 
                          return_value=output_text) as gjo_mock:
 
318
                          return_value=output_text):
4133
319
            result = client.get_status()
4134
 
        gjo_mock.assert_called_once_with(
4135
 
            'status', '--format', 'yaml', controller=False)
4136
 
        self.assertEqual(ServiceStatus, type(result))
 
320
        self.assertEqual(Status, type(result))
4137
321
        self.assertEqual(['a', 'b', 'c'], result.status)
4138
322
 
4139
323
    def test_get_status_retries_on_error(self):
4140
 
        env = SimpleEnvironment('foo')
4141
 
        client = EnvJujuClient1X(env, None, None)
 
324
        env = Environment('foo', '')
 
325
        client = EnvJujuClient(env, None, None)
4142
326
        client.attempt = 0
4143
327
 
4144
 
        def get_juju_output(command, *args, **kwargs):
 
328
        def get_juju_output(command, *args):
4145
329
            if client.attempt == 1:
4146
330
                return '"hello"'
4147
331
            client.attempt += 1
4151
335
            client.get_status()
4152
336
 
4153
337
    def test_get_status_raises_on_timeout_1(self):
4154
 
        env = SimpleEnvironment('foo')
4155
 
        client = EnvJujuClient1X(env, None, None)
 
338
        env = Environment('foo', '')
 
339
        client = EnvJujuClient(env, None, None)
4156
340
 
4157
 
        def get_juju_output(command, *args, **kwargs):
 
341
        def get_juju_output(command):
4158
342
            raise subprocess.CalledProcessError(1, command)
4159
343
 
4160
344
        with patch.object(client, 'get_juju_output',
4166
350
 
4167
351
    def test_get_status_raises_on_timeout_2(self):
4168
352
        env = SimpleEnvironment('foo')
4169
 
        client = EnvJujuClient1X(env, None, None)
 
353
        client = EnvJujuClient(env, None, None)
4170
354
        with patch('jujupy.until_timeout', return_value=iter([1])) as mock_ut:
4171
355
            with patch.object(client, 'get_juju_output',
4172
356
                              side_effect=StopIteration):
4174
358
                    client.get_status(500)
4175
359
        mock_ut.assert_called_with(500)
4176
360
 
4177
 
    def test_get_status_controller(self):
4178
 
        output_text = """\
4179
 
            - a
4180
 
            - b
4181
 
            - c
4182
 
        """
4183
 
        env = SimpleEnvironment('foo')
4184
 
        client = EnvJujuClient1X(env, None, None)
4185
 
        with patch.object(client, 'get_juju_output',
4186
 
                          return_value=output_text) as gjo_mock:
4187
 
            client.get_status(controller=True)
4188
 
        gjo_mock.assert_called_once_with(
4189
 
            'status', '--format', 'yaml', controller=True)
4190
 
 
4191
361
    @staticmethod
4192
362
    def make_status_yaml(key, machine_value, unit_value):
4193
363
        return dedent("""\
4202
372
        """.format(key, machine_value, unit_value))
4203
373
 
4204
374
    def test_deploy_non_joyent(self):
4205
 
        env = EnvJujuClient1X(
 
375
        env = EnvJujuClient(
4206
376
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4207
377
        with patch.object(env, 'juju') as mock_juju:
4208
378
            env.deploy('mondogb')
4209
379
        mock_juju.assert_called_with('deploy', ('mondogb',))
4210
380
 
4211
381
    def test_deploy_joyent(self):
4212
 
        env = EnvJujuClient1X(
 
382
        env = EnvJujuClient(
4213
383
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4214
384
        with patch.object(env, 'juju') as mock_juju:
4215
385
            env.deploy('mondogb')
4216
386
        mock_juju.assert_called_with('deploy', ('mondogb',))
4217
387
 
4218
 
    def test_deploy_repository(self):
4219
 
        env = EnvJujuClient1X(
4220
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4221
 
        with patch.object(env, 'juju') as mock_juju:
4222
 
            env.deploy('mondogb', '/home/jrandom/repo')
4223
 
        mock_juju.assert_called_with(
4224
 
            'deploy', ('mondogb', '--repository', '/home/jrandom/repo'))
4225
 
 
4226
 
    def test_deploy_to(self):
4227
 
        env = EnvJujuClient1X(
4228
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4229
 
        with patch.object(env, 'juju') as mock_juju:
4230
 
            env.deploy('mondogb', to='0')
4231
 
        mock_juju.assert_called_with(
4232
 
            'deploy', ('mondogb', '--to', '0'))
4233
 
 
4234
 
    def test_deploy_service(self):
4235
 
        env = EnvJujuClient1X(
4236
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4237
 
        with patch.object(env, 'juju') as mock_juju:
4238
 
            env.deploy('local:mondogb', service='my-mondogb')
4239
 
        mock_juju.assert_called_with(
4240
 
            'deploy', ('local:mondogb', 'my-mondogb',))
4241
 
 
4242
 
    def test_upgrade_charm(self):
4243
 
        client = EnvJujuClient1X(
4244
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4245
 
        with patch.object(client, 'juju') as mock_juju:
4246
 
            client.upgrade_charm('foo-service',
4247
 
                                 '/bar/repository/angsty/mongodb')
4248
 
        mock_juju.assert_called_once_with(
4249
 
            'upgrade-charm', ('foo-service', '--repository',
4250
 
                              '/bar/repository',))
4251
 
 
4252
 
    def test_remove_service(self):
4253
 
        env = EnvJujuClient1X(
4254
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4255
 
        with patch.object(env, 'juju') as mock_juju:
4256
 
            env.remove_service('mondogb')
4257
 
        mock_juju.assert_called_with('destroy-service', ('mondogb',))
4258
 
 
4259
 
    def test_status_until_always_runs_once(self):
4260
 
        client = EnvJujuClient1X(
4261
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4262
 
        status_txt = self.make_status_yaml('agent-state', 'started', 'started')
4263
 
        with patch.object(client, 'get_juju_output', return_value=status_txt):
4264
 
            result = list(client.status_until(-1))
4265
 
        self.assertEqual(
4266
 
            [r.status for r in result], [Status.from_text(status_txt).status])
4267
 
 
4268
 
    def test_status_until_timeout(self):
4269
 
        client = EnvJujuClient1X(
4270
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
4271
 
        status_txt = self.make_status_yaml('agent-state', 'started', 'started')
4272
 
        status_yaml = yaml.safe_load(status_txt)
4273
 
 
4274
 
        def until_timeout_stub(timeout, start=None):
4275
 
            return iter([None, None])
4276
 
 
4277
 
        with patch.object(client, 'get_juju_output', return_value=status_txt):
4278
 
            with patch('jujupy.until_timeout',
4279
 
                       side_effect=until_timeout_stub) as ut_mock:
4280
 
                result = list(client.status_until(30, 70))
4281
 
        self.assertEqual(
4282
 
            [r.status for r in result], [status_yaml] * 3)
4283
 
        # until_timeout is called by status as well as status_until.
4284
 
        self.assertEqual(ut_mock.mock_calls,
4285
 
                         [call(60), call(30, start=70), call(60), call(60)])
4286
 
 
4287
 
    def test_add_ssh_machines(self):
4288
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
4289
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
4290
 
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
4291
 
        assert_juju_call(self, cc_mock, client, (
4292
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 0)
4293
 
        assert_juju_call(self, cc_mock, client, (
4294
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-bar'), 1)
4295
 
        assert_juju_call(self, cc_mock, client, (
4296
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-baz'), 2)
4297
 
        self.assertEqual(cc_mock.call_count, 3)
4298
 
 
4299
 
    def test_add_ssh_machines_retry(self):
4300
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
4301
 
        with patch('subprocess.check_call', autospec=True,
4302
 
                   side_effect=[subprocess.CalledProcessError(None, None),
4303
 
                                None, None, None]) as cc_mock:
4304
 
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
4305
 
        assert_juju_call(self, cc_mock, client, (
4306
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 0)
4307
 
        self.pause_mock.assert_called_once_with(30)
4308
 
        assert_juju_call(self, cc_mock, client, (
4309
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 1)
4310
 
        assert_juju_call(self, cc_mock, client, (
4311
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-bar'), 2)
4312
 
        assert_juju_call(self, cc_mock, client, (
4313
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-baz'), 3)
4314
 
        self.assertEqual(cc_mock.call_count, 4)
4315
 
 
4316
 
    def test_add_ssh_machines_fail_on_second_machine(self):
4317
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
4318
 
        with patch('subprocess.check_call', autospec=True, side_effect=[
4319
 
                None, subprocess.CalledProcessError(None, None), None, None
4320
 
                ]) as cc_mock:
4321
 
            with self.assertRaises(subprocess.CalledProcessError):
4322
 
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
4323
 
        assert_juju_call(self, cc_mock, client, (
4324
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 0)
4325
 
        assert_juju_call(self, cc_mock, client, (
4326
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-bar'), 1)
4327
 
        self.assertEqual(cc_mock.call_count, 2)
4328
 
 
4329
 
    def test_add_ssh_machines_fail_on_second_attempt(self):
4330
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
4331
 
        with patch('subprocess.check_call', autospec=True, side_effect=[
4332
 
                subprocess.CalledProcessError(None, None),
4333
 
                subprocess.CalledProcessError(None, None)]) as cc_mock:
4334
 
            with self.assertRaises(subprocess.CalledProcessError):
4335
 
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
4336
 
        assert_juju_call(self, cc_mock, client, (
4337
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 0)
4338
 
        assert_juju_call(self, cc_mock, client, (
4339
 
            'juju', '--show-log', 'add-machine', '-e', 'foo', 'ssh:m-foo'), 1)
4340
 
        self.assertEqual(cc_mock.call_count, 2)
4341
 
 
4342
388
    def test_wait_for_started(self):
4343
389
        value = self.make_status_yaml('agent-state', 'started', 'started')
4344
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
390
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4345
391
        with patch.object(client, 'get_juju_output', return_value=value):
4346
392
            client.wait_for_started()
4347
393
 
4348
394
    def test_wait_for_started_timeout(self):
4349
395
        value = self.make_status_yaml('agent-state', 'pending', 'started')
4350
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
396
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4351
397
        with patch('jujupy.until_timeout', lambda x, start=None: range(1)):
4352
398
            with patch.object(client, 'get_juju_output', return_value=value):
4353
 
                writes = []
4354
 
                with patch.object(GroupReporter, '_write', autospec=True,
4355
 
                                  side_effect=lambda _, s: writes.append(s)):
4356
 
                    with self.assertRaisesRegexp(
4357
 
                            Exception,
4358
 
                            'Timed out waiting for agents to start in local'):
 
399
                with self.assertRaisesRegexp(
 
400
                        Exception,
 
401
                        'Timed out waiting for agents to start in local'):
 
402
                    with patch('logging.error'):
4359
403
                        client.wait_for_started()
4360
 
                self.assertEqual(writes, ['pending: 0', ' .', '\n'])
4361
404
 
4362
405
    def test_wait_for_started_start(self):
4363
406
        value = self.make_status_yaml('agent-state', 'started', 'pending')
4364
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
407
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4365
408
        now = datetime.now() + timedelta(days=1)
4366
409
        with patch('utility.until_timeout.now', return_value=now):
4367
410
            with patch.object(client, 'get_juju_output', return_value=value):
4368
 
                writes = []
4369
 
                with patch.object(GroupReporter, '_write', autospec=True,
4370
 
                                  side_effect=lambda _, s: writes.append(s)):
4371
 
                    with self.assertRaisesRegexp(
4372
 
                            Exception,
4373
 
                            'Timed out waiting for agents to start in local'):
 
411
                with self.assertRaisesRegexp(
 
412
                        Exception,
 
413
                        'Timed out waiting for agents to start in local'):
 
414
                    with patch('logging.error'):
4374
415
                        client.wait_for_started(start=now - timedelta(1200))
4375
 
                self.assertEqual(writes, ['pending: jenkins/0', '\n'])
4376
416
 
4377
417
    def test_wait_for_started_logs_status(self):
4378
418
        value = self.make_status_yaml('agent-state', 'pending', 'started')
4379
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
419
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4380
420
        with patch.object(client, 'get_juju_output', return_value=value):
4381
 
            writes = []
4382
 
            with patch.object(GroupReporter, '_write', autospec=True,
4383
 
                              side_effect=lambda _, s: writes.append(s)):
4384
 
                with self.assertRaisesRegexp(
4385
 
                        Exception,
4386
 
                        'Timed out waiting for agents to start in local'):
 
421
            with self.assertRaisesRegexp(
 
422
                    Exception,
 
423
                    'Timed out waiting for agents to start in local'):
 
424
                with patch('logging.error') as le_mock:
4387
425
                    client.wait_for_started(0)
4388
 
            self.assertEqual(writes, ['pending: 0', '\n'])
4389
 
        self.assertEqual(self.log_stream.getvalue(), 'ERROR %s\n' % value)
4390
 
 
4391
 
    def test_wait_for_subordinate_units(self):
4392
 
        value = dedent("""\
4393
 
            machines:
4394
 
              "0":
4395
 
                agent-state: started
4396
 
            services:
4397
 
              jenkins:
4398
 
                units:
4399
 
                  jenkins/0:
4400
 
                    subordinates:
4401
 
                      sub1/0:
4402
 
                        agent-state: started
4403
 
              ubuntu:
4404
 
                units:
4405
 
                  ubuntu/0:
4406
 
                    subordinates:
4407
 
                      sub2/0:
4408
 
                        agent-state: started
4409
 
                      sub3/0:
4410
 
                        agent-state: started
4411
 
        """)
4412
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4413
 
        now = datetime.now() + timedelta(days=1)
4414
 
        with patch('utility.until_timeout.now', return_value=now):
4415
 
            with patch.object(client, 'get_juju_output', return_value=value):
4416
 
                with patch('jujupy.GroupReporter.update') as update_mock:
4417
 
                    with patch('jujupy.GroupReporter.finish') as finish_mock:
4418
 
                        client.wait_for_subordinate_units(
4419
 
                            'jenkins', 'sub1', start=now - timedelta(1200))
4420
 
        self.assertEqual([], update_mock.call_args_list)
4421
 
        finish_mock.assert_called_once_with()
4422
 
 
4423
 
    def test_wait_for_multiple_subordinate_units(self):
4424
 
        value = dedent("""\
4425
 
            machines:
4426
 
              "0":
4427
 
                agent-state: started
4428
 
            services:
4429
 
              ubuntu:
4430
 
                units:
4431
 
                  ubuntu/0:
4432
 
                    subordinates:
4433
 
                      sub/0:
4434
 
                        agent-state: started
4435
 
                  ubuntu/1:
4436
 
                    subordinates:
4437
 
                      sub/1:
4438
 
                        agent-state: started
4439
 
        """)
4440
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4441
 
        now = datetime.now() + timedelta(days=1)
4442
 
        with patch('utility.until_timeout.now', return_value=now):
4443
 
            with patch.object(client, 'get_juju_output', return_value=value):
4444
 
                with patch('jujupy.GroupReporter.update') as update_mock:
4445
 
                    with patch('jujupy.GroupReporter.finish') as finish_mock:
4446
 
                        client.wait_for_subordinate_units(
4447
 
                            'ubuntu', 'sub', start=now - timedelta(1200))
4448
 
        self.assertEqual([], update_mock.call_args_list)
4449
 
        finish_mock.assert_called_once_with()
4450
 
 
4451
 
    def test_wait_for_subordinate_units_checks_slash_in_unit_name(self):
4452
 
        value = dedent("""\
4453
 
            machines:
4454
 
              "0":
4455
 
                agent-state: started
4456
 
            services:
4457
 
              jenkins:
4458
 
                units:
4459
 
                  jenkins/0:
4460
 
                    subordinates:
4461
 
                      sub1:
4462
 
                        agent-state: started
4463
 
        """)
4464
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4465
 
        now = datetime.now() + timedelta(days=1)
4466
 
        with patch('utility.until_timeout.now', return_value=now):
4467
 
            with patch.object(client, 'get_juju_output', return_value=value):
4468
 
                with self.assertRaisesRegexp(
4469
 
                        Exception,
4470
 
                        'Timed out waiting for agents to start in local'):
4471
 
                    client.wait_for_subordinate_units(
4472
 
                        'jenkins', 'sub1', start=now - timedelta(1200))
4473
 
 
4474
 
    def test_wait_for_subordinate_units_no_subordinate(self):
4475
 
        value = dedent("""\
4476
 
            machines:
4477
 
              "0":
4478
 
                agent-state: started
4479
 
            services:
4480
 
              jenkins:
4481
 
                units:
4482
 
                  jenkins/0:
4483
 
                    agent-state: started
4484
 
        """)
4485
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4486
 
        now = datetime.now() + timedelta(days=1)
4487
 
        with patch('utility.until_timeout.now', return_value=now):
4488
 
            with patch.object(client, 'get_juju_output', return_value=value):
4489
 
                with self.assertRaisesRegexp(
4490
 
                        Exception,
4491
 
                        'Timed out waiting for agents to start in local'):
4492
 
                    client.wait_for_subordinate_units(
4493
 
                        'jenkins', 'sub1', start=now - timedelta(1200))
4494
 
 
4495
 
    def test_wait_for_workload(self):
4496
 
        initial_status = ServiceStatus.from_text("""\
4497
 
            services:
4498
 
              jenkins:
4499
 
                units:
4500
 
                  jenkins/0:
4501
 
                    workload-status:
4502
 
                      current: waiting
4503
 
                  subordinates:
4504
 
                    ntp/0:
4505
 
                      workload-status:
4506
 
                        current: unknown
4507
 
        """)
4508
 
        final_status = Status(copy.deepcopy(initial_status.status), None)
4509
 
        final_status.status['services']['jenkins']['units']['jenkins/0'][
4510
 
            'workload-status']['current'] = 'active'
4511
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4512
 
        writes = []
4513
 
        with patch('utility.until_timeout', autospec=True, return_value=[1]):
4514
 
            with patch.object(client, 'get_status', autospec=True,
4515
 
                              side_effect=[initial_status, final_status]):
4516
 
                with patch.object(GroupReporter, '_write', autospec=True,
4517
 
                                  side_effect=lambda _, s: writes.append(s)):
4518
 
                    client.wait_for_workloads()
4519
 
        self.assertEqual(writes, ['waiting: jenkins/0', '\n'])
4520
 
 
4521
 
    def test_wait_for_workload_all_unknown(self):
4522
 
        status = Status.from_text("""\
4523
 
            services:
4524
 
              jenkins:
4525
 
                units:
4526
 
                  jenkins/0:
4527
 
                    workload-status:
4528
 
                      current: unknown
4529
 
                  subordinates:
4530
 
                    ntp/0:
4531
 
                      workload-status:
4532
 
                        current: unknown
4533
 
        """)
4534
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4535
 
        writes = []
4536
 
        with patch('utility.until_timeout', autospec=True, return_value=[]):
4537
 
            with patch.object(client, 'get_status', autospec=True,
4538
 
                              return_value=status):
4539
 
                with patch.object(GroupReporter, '_write', autospec=True,
4540
 
                                  side_effect=lambda _, s: writes.append(s)):
4541
 
                    client.wait_for_workloads(timeout=1)
4542
 
        self.assertEqual(writes, [])
4543
 
 
4544
 
    def test_wait_for_workload_no_workload_status(self):
4545
 
        status = Status.from_text("""\
4546
 
            services:
4547
 
              jenkins:
4548
 
                units:
4549
 
                  jenkins/0:
4550
 
                    agent-state: active
4551
 
        """)
4552
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4553
 
        writes = []
4554
 
        with patch('utility.until_timeout', autospec=True, return_value=[]):
4555
 
            with patch.object(client, 'get_status', autospec=True,
4556
 
                              return_value=status):
4557
 
                with patch.object(GroupReporter, '_write', autospec=True,
4558
 
                                  side_effect=lambda _, s: writes.append(s)):
4559
 
                    client.wait_for_workloads(timeout=1)
4560
 
        self.assertEqual(writes, [])
 
426
        le_mock.assert_called_once_with(value)
4561
427
 
4562
428
    def test_wait_for_ha(self):
4563
429
        value = yaml.safe_dump({
4565
431
                '0': {'state-server-member-status': 'has-vote'},
4566
432
                '1': {'state-server-member-status': 'has-vote'},
4567
433
                '2': {'state-server-member-status': 'has-vote'},
4568
 
            },
 
434
                },
4569
435
            'services': {},
4570
 
        })
4571
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
436
            })
 
437
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4572
438
        with patch.object(client, 'get_juju_output', return_value=value):
4573
439
            client.wait_for_ha()
4574
440
 
4578
444
                '0': {'state-server-member-status': 'no-vote'},
4579
445
                '1': {'state-server-member-status': 'no-vote'},
4580
446
                '2': {'state-server-member-status': 'no-vote'},
4581
 
            },
 
447
                },
4582
448
            'services': {},
4583
 
        })
4584
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4585
 
        with patch.object(client, 'get_juju_output', return_value=value):
4586
 
            writes = []
4587
 
            with patch('jujupy.until_timeout', autospec=True,
4588
 
                       return_value=[2, 1]):
4589
 
                with patch.object(GroupReporter, '_write', autospec=True,
4590
 
                                  side_effect=lambda _, s: writes.append(s)):
4591
 
                    with self.assertRaisesRegexp(
4592
 
                            Exception,
4593
 
                            'Timed out waiting for voting to be enabled.'):
4594
 
                        client.wait_for_ha()
4595
 
            self.assertEqual(writes[:2], ['no-vote: 0, 1, 2', ' .'])
4596
 
            self.assertEqual(writes[2:-1], ['.'] * (len(writes) - 3))
4597
 
            self.assertEqual(writes[-1:], ['\n'])
 
449
            })
 
450
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
 
451
        with patch('sys.stdout'):
 
452
            with patch.object(client, 'get_juju_output', return_value=value):
 
453
                with self.assertRaisesRegexp(
 
454
                        Exception,
 
455
                        'Timed out waiting for voting to be enabled.'):
 
456
                    client.wait_for_ha(0.01)
4598
457
 
4599
458
    def test_wait_for_ha_timeout(self):
4600
459
        value = yaml.safe_dump({
4601
460
            'machines': {
4602
461
                '0': {'state-server-member-status': 'has-vote'},
4603
462
                '1': {'state-server-member-status': 'has-vote'},
4604
 
            },
 
463
                },
4605
464
            'services': {},
4606
 
        })
4607
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
465
            })
 
466
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4608
467
        with patch('jujupy.until_timeout', lambda x: range(0)):
4609
468
            with patch.object(client, 'get_juju_output', return_value=value):
4610
469
                with self.assertRaisesRegexp(
4612
471
                        'Timed out waiting for voting to be enabled.'):
4613
472
                    client.wait_for_ha()
4614
473
 
4615
 
    def test_wait_for_deploy_started(self):
4616
 
        value = yaml.safe_dump({
4617
 
            'machines': {
4618
 
                '0': {'agent-state': 'started'},
4619
 
            },
4620
 
            'services': {
4621
 
                'jenkins': {
4622
 
                    'units': {
4623
 
                        'jenkins/1': {'baz': 'qux'}
4624
 
                    }
4625
 
                }
4626
 
            }
4627
 
        })
4628
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4629
 
        with patch.object(client, 'get_juju_output', return_value=value):
4630
 
            client.wait_for_deploy_started()
4631
 
 
4632
 
    def test_wait_for_deploy_started_timeout(self):
4633
 
        value = yaml.safe_dump({
4634
 
            'machines': {
4635
 
                '0': {'agent-state': 'started'},
4636
 
            },
4637
 
            'services': {},
4638
 
        })
4639
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4640
 
        with patch('jujupy.until_timeout', lambda x: range(0)):
4641
 
            with patch.object(client, 'get_juju_output', return_value=value):
4642
 
                with self.assertRaisesRegexp(
4643
 
                        Exception,
4644
 
                        'Timed out waiting for services to start.'):
4645
 
                    client.wait_for_deploy_started()
4646
 
 
4647
474
    def test_wait_for_version(self):
4648
475
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
4649
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
 
476
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
4650
477
        with patch.object(client, 'get_juju_output', return_value=value):
4651
478
            client.wait_for_version('1.17.2')
4652
479
 
4653
480
    def test_wait_for_version_timeout(self):
4654
481
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.1')
4655
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4656
 
        writes = []
4657
 
        with patch('jujupy.until_timeout', lambda x, start=None: [x]):
 
482
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
 
483
        with patch('jujupy.until_timeout', lambda x: range(0)):
4658
484
            with patch.object(client, 'get_juju_output', return_value=value):
4659
 
                with patch.object(GroupReporter, '_write', autospec=True,
4660
 
                                  side_effect=lambda _, s: writes.append(s)):
4661
 
                    with self.assertRaisesRegexp(
4662
 
                            Exception, 'Some versions did not update'):
4663
 
                        client.wait_for_version('1.17.2')
4664
 
        self.assertEqual(writes, ['1.17.1: jenkins/0', ' .', '\n'])
 
485
                with self.assertRaisesRegexp(
 
486
                        Exception, 'Some versions did not update'):
 
487
                    client.wait_for_version('1.17.2')
4665
488
 
4666
489
    def test_wait_for_version_handles_connection_error(self):
4667
490
        err = subprocess.CalledProcessError(2, 'foo')
4670
493
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
4671
494
        actions = [err, status]
4672
495
 
4673
 
        def get_juju_output_fake(*args, **kwargs):
 
496
        def get_juju_output_fake(*args):
4674
497
            action = actions.pop(0)
4675
498
            if isinstance(action, Exception):
4676
499
                raise action
4677
500
            else:
4678
501
                return action
4679
502
 
4680
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4681
 
        with patch.object(client, 'get_juju_output', get_juju_output_fake):
4682
 
            client.wait_for_version('1.17.2')
 
503
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
 
504
        output_real = 'test_jujupy.EnvJujuClient.get_juju_output'
 
505
        devnull = open(os.devnull, 'w')
 
506
        with patch('sys.stdout', devnull):
 
507
            with patch(output_real, get_juju_output_fake):
 
508
                client.wait_for_version('1.17.2')
4683
509
 
4684
510
    def test_wait_for_version_raises_non_connection_error(self):
4685
511
        err = Exception('foo')
4686
512
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
4687
513
        actions = [err, status]
4688
514
 
4689
 
        def get_juju_output_fake(*args, **kwargs):
 
515
        def get_juju_output_fake(*args):
4690
516
            action = actions.pop(0)
4691
517
            if isinstance(action, Exception):
4692
518
                raise action
4693
519
            else:
4694
520
                return action
4695
521
 
4696
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4697
 
        with patch.object(client, 'get_juju_output', get_juju_output_fake):
4698
 
            with self.assertRaisesRegexp(Exception, 'foo'):
4699
 
                client.wait_for_version('1.17.2')
4700
 
 
4701
 
    def test_wait_for_just_machine_0(self):
4702
 
        value = yaml.safe_dump({
4703
 
            'machines': {
4704
 
                '0': {'agent-state': 'started'},
4705
 
            },
4706
 
        })
4707
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4708
 
        with patch.object(client, 'get_juju_output', return_value=value):
4709
 
            client.wait_for('machines-not-0', 'none')
4710
 
 
4711
 
    def test_wait_for_just_machine_0_timeout(self):
4712
 
        value = yaml.safe_dump({
4713
 
            'machines': {
4714
 
                '0': {'agent-state': 'started'},
4715
 
                '1': {'agent-state': 'started'},
4716
 
            },
4717
 
        })
4718
 
        client = EnvJujuClient1X(SimpleEnvironment('local'), None, None)
4719
 
        with patch.object(client, 'get_juju_output', return_value=value), \
4720
 
            patch('jujupy.until_timeout', lambda x: range(0)), \
4721
 
            self.assertRaisesRegexp(
4722
 
                Exception,
4723
 
                'Timed out waiting for machines-not-0'):
4724
 
            client.wait_for('machines-not-0', 'none')
4725
 
 
4726
 
    def test_set_model_constraints(self):
4727
 
        client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
4728
 
        with patch.object(client, 'juju') as juju_mock:
4729
 
            client.set_model_constraints({'bar': 'baz'})
4730
 
        juju_mock.assert_called_once_with('set-constraints', ('bar=baz',))
4731
 
 
4732
 
    def test_get_model_config(self):
4733
 
        env = SimpleEnvironment('foo', None)
4734
 
        fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
4735
 
        client = EnvJujuClient1X(env, None, 'juju')
4736
 
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
4737
 
            result = client.get_model_config()
4738
 
        assert_juju_call(
4739
 
            self, po_mock, client, (
4740
 
                'juju', '--show-log', 'get-env', '-e', 'foo'))
4741
 
        self.assertEqual({'bar': 'baz'}, result)
 
522
        client = EnvJujuClient(SimpleEnvironment('local'), None, None)
 
523
        output_real = 'test_jujupy.EnvJujuClient.get_juju_output'
 
524
        devnull = open(os.devnull, 'w')
 
525
        with patch('sys.stdout', devnull):
 
526
            with patch(output_real, get_juju_output_fake):
 
527
                with self.assertRaisesRegexp(Exception, 'foo'):
 
528
                    client.wait_for_version('1.17.2')
4742
529
 
4743
530
    def test_get_env_option(self):
4744
 
        env = SimpleEnvironment('foo', None)
4745
 
        fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
4746
 
        client = EnvJujuClient1X(env, None, 'juju')
4747
 
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
 
531
        env = Environment('foo', '')
 
532
        client = EnvJujuClient(env, None, None)
 
533
        with patch('subprocess.check_output') as mock:
 
534
            mock.return_value = 'https://example.org/juju/tools'
4748
535
            result = client.get_env_option('tools-metadata-url')
4749
536
        self.assertEqual(
4750
537
            mock.call_args[0][0],
4753
540
        self.assertEqual('https://example.org/juju/tools', result)
4754
541
 
4755
542
    def test_set_env_option(self):
4756
 
        env = SimpleEnvironment('foo')
4757
 
        client = EnvJujuClient1X(env, None, 'juju')
 
543
        env = Environment('foo', '')
 
544
        client = EnvJujuClient(env, None, None)
4758
545
        with patch('subprocess.check_call') as mock:
4759
546
            client.set_env_option(
4760
547
                'tools-metadata-url', 'https://example.org/juju/tools')
4761
 
        environ = dict(os.environ)
4762
 
        environ['JUJU_HOME'] = client.env.juju_home
4763
548
        mock.assert_called_with(
4764
549
            ('juju', '--show-log', 'set-env', '-e', 'foo',
4765
 
             'tools-metadata-url=https://example.org/juju/tools'))
4766
 
 
4767
 
    def test_set_testing_agent_metadata_url(self):
4768
 
        env = SimpleEnvironment(None, {'type': 'foo'})
4769
 
        client = EnvJujuClient1X(env, None, None)
4770
 
        with patch.object(client, 'get_env_option') as mock_get:
4771
 
            mock_get.return_value = 'https://example.org/juju/tools'
4772
 
            with patch.object(client, 'set_env_option') as mock_set:
4773
 
                client.set_testing_agent_metadata_url()
4774
 
        mock_get.assert_called_with('tools-metadata-url')
4775
 
        mock_set.assert_called_with(
4776
 
            'tools-metadata-url',
4777
 
            'https://example.org/juju/testing/tools')
4778
 
 
4779
 
    def test_set_testing_agent_metadata_url_noop(self):
4780
 
        env = SimpleEnvironment(None, {'type': 'foo'})
4781
 
        client = EnvJujuClient1X(env, None, None)
4782
 
        with patch.object(client, 'get_env_option') as mock_get:
4783
 
            mock_get.return_value = 'https://example.org/juju/testing/tools'
4784
 
            with patch.object(client, 'set_env_option') as mock_set:
4785
 
                client.set_testing_agent_metadata_url()
4786
 
        mock_get.assert_called_with('tools-metadata-url')
4787
 
        self.assertEqual(0, mock_set.call_count)
 
550
             'tools-metadata-url=https://example.org/juju/tools'),
 
551
            env=os.environ)
4788
552
 
4789
553
    def test_juju(self):
4790
 
        env = SimpleEnvironment('qux')
4791
 
        client = EnvJujuClient1X(env, None, 'juju')
4792
 
        with patch('subprocess.check_call') as mock:
4793
 
            client.juju('foo', ('bar', 'baz'))
4794
 
        environ = dict(os.environ)
4795
 
        environ['JUJU_HOME'] = client.env.juju_home
 
554
        env = Environment('qux', '')
 
555
        client = EnvJujuClient(env, None, None)
 
556
        with patch('sys.stdout') as stdout_mock:
 
557
            with patch('subprocess.check_call') as mock:
 
558
                client.juju('foo', ('bar', 'baz'))
4796
559
        mock.assert_called_with(('juju', '--show-log', 'foo', '-e', 'qux',
4797
 
                                 'bar', 'baz'))
 
560
                                 'bar', 'baz'), env=os.environ)
 
561
        stdout_mock.flush.assert_called_with()
4798
562
 
4799
563
    def test_juju_env(self):
4800
 
        env = SimpleEnvironment('qux')
4801
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4802
 
 
4803
 
        def check_path(*args, **kwargs):
4804
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
4805
 
        with patch('subprocess.check_call', side_effect=check_path):
 
564
        env = Environment('qux', '')
 
565
        client = EnvJujuClient(env, None, '/foobar/baz')
 
566
        with patch('subprocess.check_call') as cc_mock:
4806
567
            client.juju('foo', ('bar', 'baz'))
 
568
        self.assertRegexpMatches(cc_mock.call_args[1]['env']['PATH'],
 
569
                                 r'/foobar\:')
4807
570
 
4808
571
    def test_juju_no_check(self):
4809
 
        env = SimpleEnvironment('qux')
4810
 
        client = EnvJujuClient1X(env, None, 'juju')
4811
 
        environ = dict(os.environ)
4812
 
        environ['JUJU_HOME'] = client.env.juju_home
4813
 
        with patch('subprocess.call') as mock:
4814
 
            client.juju('foo', ('bar', 'baz'), check=False)
 
572
        env = Environment('qux', '')
 
573
        client = EnvJujuClient(env, None, None)
 
574
        with patch('sys.stdout') as stdout_mock:
 
575
            with patch('subprocess.call') as mock:
 
576
                client.juju('foo', ('bar', 'baz'), check=False)
4815
577
        mock.assert_called_with(('juju', '--show-log', 'foo', '-e', 'qux',
4816
 
                                 'bar', 'baz'))
 
578
                                 'bar', 'baz'), env=os.environ)
 
579
        stdout_mock.flush.assert_called_with()
4817
580
 
4818
581
    def test_juju_no_check_env(self):
4819
 
        env = SimpleEnvironment('qux')
4820
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4821
 
 
4822
 
        def check_path(*args, **kwargs):
4823
 
            self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:')
4824
 
        with patch('subprocess.call', side_effect=check_path):
 
582
        env = Environment('qux', '')
 
583
        client = EnvJujuClient(env, None, '/foobar/baz')
 
584
        with patch('subprocess.call') as call_mock:
4825
585
            client.juju('foo', ('bar', 'baz'), check=False)
 
586
        self.assertRegexpMatches(call_mock.call_args[1]['env']['PATH'],
 
587
                                 r'/foobar\:')
4826
588
 
4827
589
    def test_juju_timeout(self):
4828
 
        env = SimpleEnvironment('qux')
4829
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
 
590
        env = Environment('qux', '')
 
591
        client = EnvJujuClient(env, None, '/foobar/baz')
4830
592
        with patch('subprocess.check_call') as cc_mock:
4831
593
            client.juju('foo', ('bar', 'baz'), timeout=58)
4832
594
        self.assertEqual(cc_mock.call_args[0][0], (
4833
 
            sys.executable, get_timeout_path(), '58.00', '--', 'baz',
4834
 
            '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4835
 
 
4836
 
    def test_juju_juju_home(self):
4837
 
        env = SimpleEnvironment('qux')
4838
 
        os.environ['JUJU_HOME'] = 'foo'
4839
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4840
 
 
4841
 
        def check_home(*args, **kwargs):
4842
 
            self.assertEqual(os.environ['JUJU_HOME'], 'foo')
4843
 
            yield
4844
 
            self.assertEqual(os.environ['JUJU_HOME'], 'asdf')
4845
 
            yield
4846
 
 
4847
 
        with patch('subprocess.check_call', side_effect=check_home):
4848
 
            client.juju('foo', ('bar', 'baz'))
4849
 
            client.env.juju_home = 'asdf'
4850
 
            client.juju('foo', ('bar', 'baz'))
4851
 
 
4852
 
    def test_juju_extra_env(self):
4853
 
        env = SimpleEnvironment('qux')
4854
 
        client = EnvJujuClient1X(env, None, 'juju')
4855
 
        extra_env = {'JUJU': '/juju', 'JUJU_HOME': client.env.juju_home}
4856
 
 
4857
 
        def check_env(*args, **kwargs):
4858
 
            self.assertEqual('/juju', os.environ['JUJU'])
4859
 
 
4860
 
        with patch('subprocess.check_call', side_effect=check_env) as mock:
4861
 
            client.juju('quickstart', ('bar', 'baz'), extra_env=extra_env)
4862
 
        mock.assert_called_with(
4863
 
            ('juju', '--show-log', 'quickstart', '-e', 'qux', 'bar', 'baz'))
 
595
            'timeout', '58.00s', 'juju', '--show-log', 'foo', '-e', 'qux',
 
596
            'bar', 'baz'))
4864
597
 
4865
598
    def test_juju_backup_with_tgz(self):
4866
599
        env = SimpleEnvironment('qux')
4867
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4868
 
 
4869
 
        def check_env(*args, **kwargs):
4870
 
            self.assertEqual(os.environ['JUJU_ENV'], 'qux')
4871
 
            return 'foojuju-backup-24.tgzz'
 
600
        client = EnvJujuClient(env, None, '/foobar/baz')
4872
601
        with patch('subprocess.check_output',
4873
 
                   side_effect=check_env) as co_mock:
4874
 
            backup_file = client.backup()
 
602
                   return_value='foojuju-backup-24.tgzz') as co_mock:
 
603
            with patch('sys.stdout'):
 
604
                backup_file = client.backup()
4875
605
        self.assertEqual(backup_file, os.path.abspath('juju-backup-24.tgz'))
4876
606
        assert_juju_call(self, co_mock, client, ['juju', 'backup'])
 
607
        self.assertEqual(co_mock.mock_calls[0][2]['env']['JUJU_ENV'], 'qux')
4877
608
 
4878
609
    def test_juju_backup_with_tar_gz(self):
4879
610
        env = SimpleEnvironment('qux')
4880
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
 
611
        client = EnvJujuClient(env, None, '/foobar/baz')
4881
612
        with patch('subprocess.check_output',
4882
613
                   return_value='foojuju-backup-123-456.tar.gzbar'):
4883
 
            backup_file = client.backup()
 
614
            with patch('sys.stdout'):
 
615
                backup_file = client.backup()
4884
616
        self.assertEqual(
4885
617
            backup_file, os.path.abspath('juju-backup-123-456.tar.gz'))
4886
618
 
4887
619
    def test_juju_backup_no_file(self):
4888
620
        env = SimpleEnvironment('qux')
4889
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
 
621
        client = EnvJujuClient(env, None, '/foobar/baz')
4890
622
        with patch('subprocess.check_output', return_value=''):
4891
623
            with self.assertRaisesRegexp(
4892
624
                    Exception, 'The backup file was not found in output'):
4893
 
                client.backup()
 
625
                with patch('sys.stdout'):
 
626
                    client.backup()
4894
627
 
4895
628
    def test_juju_backup_wrong_file(self):
4896
629
        env = SimpleEnvironment('qux')
4897
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
 
630
        client = EnvJujuClient(env, None, '/foobar/baz')
4898
631
        with patch('subprocess.check_output',
4899
632
                   return_value='mumu-backup-24.tgz'):
4900
633
            with self.assertRaisesRegexp(
4901
634
                    Exception, 'The backup file was not found in output'):
4902
 
                client.backup()
4903
 
 
4904
 
    def test_juju_backup_environ(self):
4905
 
        env = SimpleEnvironment('qux')
4906
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4907
 
        environ = client._shell_environ()
4908
 
        environ['JUJU_ENV'] = client.env.environment
4909
 
 
4910
 
        def side_effect(*args, **kwargs):
4911
 
            self.assertEqual(environ, os.environ)
4912
 
            return 'foojuju-backup-123-456.tar.gzbar'
4913
 
        with patch('subprocess.check_output', side_effect=side_effect):
4914
 
            client.backup()
4915
 
            self.assertNotEqual(environ, os.environ)
4916
 
 
4917
 
    def test_restore_backup(self):
4918
 
        env = SimpleEnvironment('qux')
4919
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4920
 
        with patch.object(client, 'get_juju_output') as gjo_mock:
4921
 
            result = client.restore_backup('quxx')
4922
 
        gjo_mock.assert_called_once_with('restore', '--constraints',
4923
 
                                         'mem=2G', 'quxx')
4924
 
        self.assertIs(gjo_mock.return_value, result)
4925
 
 
4926
 
    def test_restore_backup_async(self):
4927
 
        env = SimpleEnvironment('qux')
4928
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4929
 
        with patch.object(client, 'juju_async') as gjo_mock:
4930
 
            result = client.restore_backup_async('quxx')
4931
 
        gjo_mock.assert_called_once_with(
4932
 
            'restore', ('--constraints', 'mem=2G', 'quxx'))
4933
 
        self.assertIs(gjo_mock.return_value, result)
4934
 
 
4935
 
    def test_enable_ha(self):
4936
 
        env = SimpleEnvironment('qux')
4937
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4938
 
        with patch.object(client, 'juju', autospec=True) as eha_mock:
4939
 
            client.enable_ha()
4940
 
        eha_mock.assert_called_once_with('ensure-availability', ('-n', '3'))
4941
 
 
4942
 
    def test_juju_async(self):
4943
 
        env = SimpleEnvironment('qux')
4944
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4945
 
        with patch('subprocess.Popen') as popen_class_mock:
4946
 
            with client.juju_async('foo', ('bar', 'baz')) as proc:
4947
 
                assert_juju_call(self, popen_class_mock, client, (
4948
 
                    'baz', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4949
 
                self.assertIs(proc, popen_class_mock.return_value)
4950
 
                self.assertEqual(proc.wait.call_count, 0)
4951
 
                proc.wait.return_value = 0
4952
 
        proc.wait.assert_called_once_with()
4953
 
 
4954
 
    def test_juju_async_failure(self):
4955
 
        env = SimpleEnvironment('qux')
4956
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4957
 
        with patch('subprocess.Popen') as popen_class_mock:
4958
 
            with self.assertRaises(subprocess.CalledProcessError) as err_cxt:
4959
 
                with client.juju_async('foo', ('bar', 'baz')):
4960
 
                    proc_mock = popen_class_mock.return_value
4961
 
                    proc_mock.wait.return_value = 23
4962
 
        self.assertEqual(err_cxt.exception.returncode, 23)
4963
 
        self.assertEqual(err_cxt.exception.cmd, (
4964
 
            'baz', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4965
 
 
4966
 
    def test_juju_async_environ(self):
4967
 
        env = SimpleEnvironment('qux')
4968
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4969
 
        environ = client._shell_environ()
4970
 
        proc_mock = Mock()
4971
 
        with patch('subprocess.Popen') as popen_class_mock:
4972
 
 
4973
 
            def check_environ(*args, **kwargs):
4974
 
                self.assertEqual(environ, os.environ)
4975
 
                return proc_mock
4976
 
            popen_class_mock.side_effect = check_environ
4977
 
            proc_mock.wait.return_value = 0
4978
 
            with client.juju_async('foo', ('bar', 'baz')):
4979
 
                pass
4980
 
            self.assertNotEqual(environ, os.environ)
4981
 
 
4982
 
    def test_is_jes_enabled(self):
4983
 
        env = SimpleEnvironment('qux')
4984
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4985
 
        fake_popen = FakePopen(' %s' % SYSTEM, None, 0)
4986
 
        with patch('subprocess.Popen',
4987
 
                   return_value=fake_popen) as po_mock:
4988
 
            self.assertFalse(client.is_jes_enabled())
4989
 
        assert_juju_call(self, po_mock, client, (
4990
 
            'baz', '--show-log', 'help', 'commands'))
4991
 
        # Juju 1.25 uses the system command.
4992
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4993
 
        fake_popen = FakePopen(SYSTEM, None, 0)
4994
 
        with patch('subprocess.Popen', autospec=True,
4995
 
                   return_value=fake_popen):
4996
 
            self.assertTrue(client.is_jes_enabled())
4997
 
        # Juju 1.26 uses the controller command.
4998
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4999
 
        fake_popen = FakePopen(CONTROLLER, None, 0)
5000
 
        with patch('subprocess.Popen', autospec=True,
5001
 
                   return_value=fake_popen):
5002
 
            self.assertTrue(client.is_jes_enabled())
5003
 
 
5004
 
    def test_get_jes_command(self):
5005
 
        env = SimpleEnvironment('qux')
5006
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
5007
 
        # Juju 1.24 and older do not have a JES command. It is an error
5008
 
        # to call get_jes_command when is_jes_enabled is False
5009
 
        fake_popen = FakePopen(' %s' % SYSTEM, None, 0)
5010
 
        with patch('subprocess.Popen',
5011
 
                   return_value=fake_popen) as po_mock:
5012
 
            with self.assertRaises(JESNotSupported):
5013
 
                client.get_jes_command()
5014
 
        assert_juju_call(self, po_mock, client, (
5015
 
            'baz', '--show-log', 'help', 'commands'))
5016
 
        # Juju 2.x uses the 'controller kill' command.
5017
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
5018
 
        fake_popen = FakePopen(CONTROLLER, None, 0)
5019
 
        with patch('subprocess.Popen', autospec=True,
5020
 
                   return_value=fake_popen):
5021
 
            self.assertEqual(CONTROLLER, client.get_jes_command())
5022
 
        # Juju 1.26 uses the destroy-controller command.
5023
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
5024
 
        fake_popen = FakePopen(KILL_CONTROLLER, None, 0)
5025
 
        with patch('subprocess.Popen', autospec=True,
5026
 
                   return_value=fake_popen):
5027
 
            self.assertEqual(KILL_CONTROLLER, client.get_jes_command())
5028
 
        # Juju 1.25 uses the 'system kill' command.
5029
 
        client = EnvJujuClient1X(env, None, '/foobar/baz')
5030
 
        fake_popen = FakePopen(SYSTEM, None, 0)
5031
 
        with patch('subprocess.Popen', autospec=True,
5032
 
                   return_value=fake_popen):
5033
 
            self.assertEqual(
5034
 
                SYSTEM, client.get_jes_command())
5035
 
 
5036
 
    def test_get_juju_timings(self):
5037
 
        env = SimpleEnvironment('foo')
5038
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
5039
 
        client._backend.juju_timings = {("juju", "op1"): [1],
5040
 
                                        ("juju", "op2"): [2]}
5041
 
        flattened_timings = client.get_juju_timings()
5042
 
        expected = {"juju op1": [1], "juju op2": [2]}
5043
 
        self.assertEqual(flattened_timings, expected)
5044
 
 
5045
 
    def test_deploy_bundle_1x(self):
5046
 
        client = EnvJujuClient1X(SimpleEnvironment('an_env', None),
5047
 
                                 '1.23-series-arch', None)
5048
 
        with patch.object(client, 'juju') as mock_juju:
5049
 
            client.deploy_bundle('bundle:~juju-qa/some-bundle')
5050
 
        mock_juju.assert_called_with(
5051
 
            'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
5052
 
                         '3600', '--config', 'bundle:~juju-qa/some-bundle'),
5053
 
            False
5054
 
        )
5055
 
 
5056
 
    def test_deploy_bundle_template(self):
5057
 
        client = EnvJujuClient1X(SimpleEnvironment('an_env', None),
5058
 
                                 '1.23-series-arch', None)
5059
 
        with patch.object(client, 'juju') as mock_juju:
5060
 
            client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
5061
 
        mock_juju.assert_called_with(
5062
 
            'deployer', (
5063
 
                '--debug', '--deploy-delay', '10', '--timeout', '3600',
5064
 
                '--config', 'bundle:~juju-qa/some-lxc-bundle',
5065
 
                ),
5066
 
            False)
5067
 
 
5068
 
    def test_deployer(self):
5069
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5070
 
                                 '1.23-series-arch', None)
5071
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5072
 
            client.deployer('bundle:~juju-qa/some-bundle')
5073
 
        mock.assert_called_with(
5074
 
            'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
5075
 
                         '3600', '--config', 'bundle:~juju-qa/some-bundle'),
5076
 
            True
5077
 
        )
5078
 
 
5079
 
    def test_deployer_template(self):
5080
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5081
 
                                 '1.23-series-arch', None)
5082
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5083
 
            client.deployer('bundle:~juju-qa/some-{container}-bundle')
5084
 
        mock.assert_called_with(
5085
 
            'deployer', (
5086
 
                '--debug', '--deploy-delay', '10', '--timeout', '3600',
5087
 
                '--config', 'bundle:~juju-qa/some-lxc-bundle',
5088
 
                ), True
5089
 
        )
5090
 
 
5091
 
    def test_deployer_with_bundle_name(self):
5092
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5093
 
                                 '1.23-series-arch', None)
5094
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5095
 
            client.deployer('bundle:~juju-qa/some-bundle', 'name')
5096
 
        mock.assert_called_with(
5097
 
            'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
5098
 
                         '3600', '--config', 'bundle:~juju-qa/some-bundle',
5099
 
                         'name'),
5100
 
            True
5101
 
        )
5102
 
 
5103
 
    def test_quickstart_maas(self):
5104
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'maas'}),
5105
 
                                 '1.23-series-arch', '/juju')
5106
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5107
 
            client.quickstart('bundle:~juju-qa/some-bundle')
5108
 
        mock.assert_called_with(
5109
 
            'quickstart',
5110
 
            ('--constraints', 'mem=2G', '--no-browser',
5111
 
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
5112
 
        )
5113
 
 
5114
 
    def test_quickstart_local(self):
5115
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5116
 
                                 '1.23-series-arch', '/juju')
5117
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5118
 
            client.quickstart('bundle:~juju-qa/some-bundle')
5119
 
        mock.assert_called_with(
5120
 
            'quickstart',
5121
 
            ('--constraints', 'mem=2G', '--no-browser',
5122
 
             'bundle:~juju-qa/some-bundle'), True, extra_env={'JUJU': '/juju'}
5123
 
        )
5124
 
 
5125
 
    def test_quickstart_nonlocal(self):
5126
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'nonlocal'}),
5127
 
                                 '1.23-series-arch', '/juju')
5128
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5129
 
            client.quickstart('bundle:~juju-qa/some-bundle')
5130
 
        mock.assert_called_with(
5131
 
            'quickstart',
5132
 
            ('--constraints', 'mem=2G', '--no-browser',
5133
 
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
5134
 
        )
5135
 
 
5136
 
    def test_quickstart_template(self):
5137
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5138
 
                                 '1.23-series-arch', '/juju')
5139
 
        with patch.object(EnvJujuClient1X, 'juju') as mock:
5140
 
            client.quickstart('bundle:~juju-qa/some-{container}-bundle')
5141
 
        mock.assert_called_with(
5142
 
            'quickstart', (
5143
 
                '--constraints', 'mem=2G', '--no-browser',
5144
 
                'bundle:~juju-qa/some-lxc-bundle'),
5145
 
            True, extra_env={'JUJU': '/juju'})
5146
 
 
5147
 
    def test_list_models(self):
5148
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5149
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5150
 
        client.list_models()
5151
 
        self.assertEqual(
5152
 
            'INFO The model is environment foo\n',
5153
 
            self.log_stream.getvalue())
5154
 
 
5155
 
    def test__get_models(self):
5156
 
        data = """\
5157
 
            - name: foo
5158
 
              model-uuid: aaaa
5159
 
            - name: bar
5160
 
              model-uuid: bbbb
5161
 
        """
5162
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5163
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5164
 
        with patch.object(client, 'get_juju_output', return_value=data):
5165
 
            models = client._get_models()
5166
 
            self.assertEqual(
5167
 
                [{'name': 'foo', 'model-uuid': 'aaaa'},
5168
 
                 {'name': 'bar', 'model-uuid': 'bbbb'}],
5169
 
                models)
5170
 
 
5171
 
    def test__get_models_exception(self):
5172
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5173
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5174
 
        with patch.object(client, 'get_juju_output',
5175
 
                          side_effect=subprocess.CalledProcessError('a', 'b')):
5176
 
            self.assertEqual([], client._get_models())
5177
 
 
5178
 
    def test_get_models(self):
5179
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5180
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5181
 
        self.assertEqual({}, client.get_models())
5182
 
 
5183
 
    def test_iter_model_clients(self):
5184
 
        data = """\
5185
 
            - name: foo
5186
 
              model-uuid: aaaa
5187
 
              owner: admin@local
5188
 
            - name: bar
5189
 
              model-uuid: bbbb
5190
 
              owner: admin@local
5191
 
        """
5192
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5193
 
        with patch.object(client, 'get_juju_output', return_value=data):
5194
 
            model_clients = list(client.iter_model_clients())
5195
 
        self.assertEqual(2, len(model_clients))
5196
 
        self.assertIs(client, model_clients[0])
5197
 
        self.assertEqual('bar', model_clients[1].env.environment)
5198
 
 
5199
 
    def test_get_controller_model_name_no_models(self):
5200
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5201
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5202
 
        with patch.object(client, 'get_models', return_value={}):
5203
 
            controller_name = client.get_controller_model_name()
5204
 
        self.assertEqual('foo', controller_name)
5205
 
 
5206
 
    def test_get_controller_client(self):
5207
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), {'bar': 'baz'},
5208
 
                                 'myhome')
5209
 
        controller_client = client.get_controller_client()
5210
 
        self.assertIs(client, controller_client)
5211
 
 
5212
 
    def test_list_controllers(self):
5213
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5214
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5215
 
        client.list_controllers()
5216
 
        self.assertEqual(
5217
 
            'INFO The controller is environment foo\n',
5218
 
            self.log_stream.getvalue())
5219
 
 
5220
 
    def test_get_controller_endpoint_ipv4(self):
5221
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5222
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5223
 
        with patch.object(client, 'get_juju_output',
5224
 
                          return_value='10.0.0.1:17070') as gjo_mock:
5225
 
            endpoint = client.get_controller_endpoint()
5226
 
        self.assertEqual('10.0.0.1', endpoint)
5227
 
        gjo_mock.assert_called_once_with('api-endpoints')
5228
 
 
5229
 
    def test_get_controller_endpoint_ipv6(self):
5230
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5231
 
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5232
 
        with patch.object(client, 'get_juju_output',
5233
 
                          return_value='[::1]:17070') as gjo_mock:
5234
 
            endpoint = client.get_controller_endpoint()
5235
 
        self.assertEqual('::1', endpoint)
5236
 
        gjo_mock.assert_called_once_with('api-endpoints')
5237
 
 
5238
 
    def test_action_do(self):
5239
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5240
 
                                 '1.23-series-arch', None)
5241
 
        with patch.object(EnvJujuClient1X, 'get_juju_output') as mock:
5242
 
            mock.return_value = \
5243
 
                "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9"
5244
 
            id = client.action_do("foo/0", "myaction", "param=5")
5245
 
            self.assertEqual(id, "5a92ec93-d4be-4399-82dc-7431dbfd08f9")
5246
 
        mock.assert_called_once_with(
5247
 
            'action do', 'foo/0', 'myaction', "param=5"
5248
 
        )
5249
 
 
5250
 
    def test_action_do_error(self):
5251
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5252
 
                                 '1.23-series-arch', None)
5253
 
        with patch.object(EnvJujuClient1X, 'get_juju_output') as mock:
5254
 
            mock.return_value = "some bad text"
5255
 
            with self.assertRaisesRegexp(Exception,
5256
 
                                         "Action id not found in output"):
5257
 
                client.action_do("foo/0", "myaction", "param=5")
5258
 
 
5259
 
    def test_action_fetch(self):
5260
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5261
 
                                 '1.23-series-arch', None)
5262
 
        with patch.object(EnvJujuClient1X, 'get_juju_output') as mock:
5263
 
            ret = "status: completed\nfoo: bar"
5264
 
            mock.return_value = ret
5265
 
            out = client.action_fetch("123")
5266
 
            self.assertEqual(out, ret)
5267
 
        mock.assert_called_once_with(
5268
 
            'action fetch', '123', "--wait", "1m"
5269
 
        )
5270
 
 
5271
 
    def test_action_fetch_timeout(self):
5272
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5273
 
                                 '1.23-series-arch', None)
5274
 
        ret = "status: pending\nfoo: bar"
5275
 
        with patch.object(EnvJujuClient1X,
5276
 
                          'get_juju_output', return_value=ret):
5277
 
            with self.assertRaisesRegexp(Exception,
5278
 
                                         "timed out waiting for action"):
5279
 
                client.action_fetch("123")
5280
 
 
5281
 
    def test_action_do_fetch(self):
5282
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5283
 
                                 '1.23-series-arch', None)
5284
 
        with patch.object(EnvJujuClient1X, 'get_juju_output') as mock:
5285
 
            ret = "status: completed\nfoo: bar"
5286
 
            # setting side_effect to an iterable will return the next value
5287
 
            # from the list each time the function is called.
5288
 
            mock.side_effect = [
5289
 
                "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9",
5290
 
                ret]
5291
 
            out = client.action_do_fetch("foo/0", "myaction", "param=5")
5292
 
            self.assertEqual(out, ret)
5293
 
 
5294
 
    def test_run(self):
5295
 
        env = SimpleEnvironment('name', {}, 'foo')
5296
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5297
 
        run_list = [
5298
 
            {"MachineId": "1",
5299
 
             "Stdout": "Linux\n",
5300
 
             "ReturnCode": 255,
5301
 
             "Stderr": "Permission denied (publickey,password)"}]
5302
 
        run_output = json.dumps(run_list)
5303
 
        with patch.object(client._backend, 'get_juju_output',
5304
 
                          return_value=run_output) as gjo_mock:
5305
 
            result = client.run(('wname',), applications=['foo', 'bar'])
5306
 
        self.assertEqual(run_list, result)
5307
 
        gjo_mock.assert_called_once_with(
5308
 
            'run', ('--format', 'json', '--service', 'foo,bar', 'wname'),
5309
 
            frozenset(
5310
 
                ['address-allocation', 'migration']),
5311
 
            'foo', 'name', user_name=None)
5312
 
 
5313
 
    def test_list_space(self):
5314
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5315
 
                                 '1.23-series-arch', None)
5316
 
        yaml_dict = {'foo': 'bar'}
5317
 
        output = yaml.safe_dump(yaml_dict)
5318
 
        with patch.object(client, 'get_juju_output', return_value=output,
5319
 
                          autospec=True) as gjo_mock:
5320
 
            result = client.list_space()
5321
 
        self.assertEqual(result, yaml_dict)
5322
 
        gjo_mock.assert_called_once_with('space list')
5323
 
 
5324
 
    def test_add_space(self):
5325
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5326
 
                                 '1.23-series-arch', None)
5327
 
        with patch.object(client, 'juju', autospec=True) as juju_mock:
5328
 
            client.add_space('foo-space')
5329
 
        juju_mock.assert_called_once_with('space create', ('foo-space'))
5330
 
 
5331
 
    def test_add_subnet(self):
5332
 
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5333
 
                                 '1.23-series-arch', None)
5334
 
        with patch.object(client, 'juju', autospec=True) as juju_mock:
5335
 
            client.add_subnet('bar-subnet', 'foo-space')
5336
 
        juju_mock.assert_called_once_with('subnet add',
5337
 
                                          ('bar-subnet', 'foo-space'))
5338
 
 
5339
 
    def test__shell_environ_uses_pathsep(self):
5340
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None,
5341
 
                                 'foo/bar/juju')
5342
 
        with patch('os.pathsep', '!'):
5343
 
            environ = client._shell_environ()
5344
 
        self.assertRegexpMatches(environ['PATH'], r'foo/bar\!')
5345
 
 
5346
 
    def test_set_config(self):
5347
 
        client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
5348
 
        with patch.object(client, 'juju') as juju_mock:
5349
 
            client.set_config('foo', {'bar': 'baz'})
5350
 
        juju_mock.assert_called_once_with('set', ('foo', 'bar=baz'))
5351
 
 
5352
 
    def test_get_config(self):
5353
 
        def output(*args, **kwargs):
5354
 
            return yaml.safe_dump({
5355
 
                'charm': 'foo',
5356
 
                'service': 'foo',
5357
 
                'settings': {
5358
 
                    'dir': {
5359
 
                        'default': 'true',
5360
 
                        'description': 'bla bla',
5361
 
                        'type': 'string',
5362
 
                        'value': '/tmp/charm-dir',
5363
 
                    }
5364
 
                }
5365
 
            })
5366
 
        expected = yaml.safe_load(output())
5367
 
        client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
5368
 
        with patch.object(client, 'get_juju_output',
5369
 
                          side_effect=output) as gjo_mock:
5370
 
            results = client.get_config('foo')
5371
 
        self.assertEqual(expected, results)
5372
 
        gjo_mock.assert_called_once_with('get', 'foo')
5373
 
 
5374
 
    def test_get_service_config(self):
5375
 
        def output(*args, **kwargs):
5376
 
            return yaml.safe_dump({
5377
 
                'charm': 'foo',
5378
 
                'service': 'foo',
5379
 
                'settings': {
5380
 
                    'dir': {
5381
 
                        'default': 'true',
5382
 
                        'description': 'bla bla',
5383
 
                        'type': 'string',
5384
 
                        'value': '/tmp/charm-dir',
5385
 
                    }
5386
 
                }
5387
 
            })
5388
 
        expected = yaml.safe_load(output())
5389
 
        client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
5390
 
        with patch.object(client, 'get_juju_output', side_effect=output):
5391
 
            results = client.get_service_config('foo')
5392
 
        self.assertEqual(expected, results)
5393
 
 
5394
 
    def test_get_service_config_timesout(self):
5395
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, '/foo')
5396
 
        with patch('jujupy.until_timeout', return_value=range(0)):
5397
 
            with self.assertRaisesRegexp(
5398
 
                    Exception, 'Timed out waiting for juju get'):
5399
 
                client.get_service_config('foo')
5400
 
 
5401
 
    def test_ssh_keys(self):
5402
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5403
 
        given_output = 'ssh keys output'
5404
 
        with patch.object(client, 'get_juju_output', autospec=True,
5405
 
                          return_value=given_output) as mock:
5406
 
            output = client.ssh_keys()
5407
 
        self.assertEqual(output, given_output)
5408
 
        mock.assert_called_once_with('authorized-keys list')
5409
 
 
5410
 
    def test_ssh_keys_full(self):
5411
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5412
 
        given_output = 'ssh keys full output'
5413
 
        with patch.object(client, 'get_juju_output', autospec=True,
5414
 
                          return_value=given_output) as mock:
5415
 
            output = client.ssh_keys(full=True)
5416
 
        self.assertEqual(output, given_output)
5417
 
        mock.assert_called_once_with('authorized-keys list', '--full')
5418
 
 
5419
 
    def test_add_ssh_key(self):
5420
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5421
 
        with patch.object(client, 'get_juju_output', autospec=True,
5422
 
                          return_value='') as mock:
5423
 
            output = client.add_ssh_key('ak', 'bk')
5424
 
        self.assertEqual(output, '')
5425
 
        mock.assert_called_once_with(
5426
 
            'authorized-keys add', 'ak', 'bk', merge_stderr=True)
5427
 
 
5428
 
    def test_remove_ssh_key(self):
5429
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5430
 
        with patch.object(client, 'get_juju_output', autospec=True,
5431
 
                          return_value='') as mock:
5432
 
            output = client.remove_ssh_key('ak', 'bk')
5433
 
        self.assertEqual(output, '')
5434
 
        mock.assert_called_once_with(
5435
 
            'authorized-keys delete', 'ak', 'bk', merge_stderr=True)
5436
 
 
5437
 
    def test_import_ssh_key(self):
5438
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5439
 
        with patch.object(client, 'get_juju_output', autospec=True,
5440
 
                          return_value='') as mock:
5441
 
            output = client.import_ssh_key('gh:au', 'lp:bu')
5442
 
        self.assertEqual(output, '')
5443
 
        mock.assert_called_once_with(
5444
 
            'authorized-keys import', 'gh:au', 'lp:bu', merge_stderr=True)
 
635
                with patch('sys.stdout'):
 
636
                    client.backup()
5445
637
 
5446
638
 
5447
639
class TestUniquifyLocal(TestCase):
5493
685
 
5494
686
 
5495
687
@contextmanager
5496
 
def bootstrap_context(client=None):
 
688
def bootstrap_context(client):
5497
689
    # Avoid unnecessary syscalls.
5498
690
    with patch('jujupy.check_free_disk_space'):
5499
691
        with scoped_environ():
5502
694
                yield fake_home
5503
695
 
5504
696
 
5505
 
class TestJesHomePath(TestCase):
5506
 
 
5507
 
    def test_jes_home_path(self):
5508
 
        path = jes_home_path('/home/jrandom/foo', 'bar')
5509
 
        self.assertEqual(path, '/home/jrandom/foo/jes-homes/bar')
5510
 
 
5511
 
 
5512
 
class TestGetCachePath(TestCase):
5513
 
 
5514
 
    def test_get_cache_path(self):
5515
 
        path = get_cache_path('/home/jrandom/foo')
5516
 
        self.assertEqual(path, '/home/jrandom/foo/environments/cache.yaml')
5517
 
 
5518
 
    def test_get_cache_path_models(self):
5519
 
        path = get_cache_path('/home/jrandom/foo', models=True)
5520
 
        self.assertEqual(path, '/home/jrandom/foo/models/cache.yaml')
5521
 
 
5522
 
 
5523
 
def stub_bootstrap(client):
5524
 
    jenv_path = get_jenv_path(client.env.juju_home, 'qux')
 
697
def stub_bootstrap(upload_tools=False):
 
698
    jenv_path = get_jenv_path(os.environ['JUJU_HOME'], 'qux')
5525
699
    os.mkdir(os.path.dirname(jenv_path))
5526
700
    with open(jenv_path, 'w') as f:
5527
701
        f.write('Bogus jenv')
5528
702
 
5529
703
 
5530
 
class TestMakeSafeConfig(TestCase):
5531
 
 
5532
 
    def test_default(self):
5533
 
        client = fake_juju_client(JujuData('foo', {'type': 'bar'},
5534
 
                                           juju_home='foo'),
5535
 
                                  version='1.2-alpha3-asdf-asdf')
5536
 
        config = make_safe_config(client)
5537
 
        self.assertEqual({
5538
 
            'name': 'foo',
5539
 
            'type': 'bar',
5540
 
            'test-mode': True,
5541
 
            'agent-version': '1.2-alpha3',
5542
 
            }, config)
5543
 
 
5544
 
    def test_local(self):
5545
 
        with temp_dir() as juju_home:
5546
 
            env = JujuData('foo', {'type': 'local'}, juju_home=juju_home)
5547
 
            client = fake_juju_client(env)
5548
 
            with patch('jujupy.check_free_disk_space'):
5549
 
                config = make_safe_config(client)
5550
 
        self.assertEqual(get_local_root(client.env.juju_home, client.env),
5551
 
                         config['root-dir'])
5552
 
 
5553
 
    def test_bootstrap_replaces_agent_version(self):
5554
 
        client = fake_juju_client(JujuData('foo', {'type': 'bar'},
5555
 
                                  juju_home='foo'))
5556
 
        client.bootstrap_replaces = {'agent-version'}
5557
 
        self.assertNotIn('agent-version', make_safe_config(client))
5558
 
        client.env.config['agent-version'] = '1.23'
5559
 
        self.assertNotIn('agent-version', make_safe_config(client))
5560
 
 
5561
 
 
5562
 
class TestTempBootstrapEnv(FakeHomeTestCase):
5563
 
 
5564
 
    @staticmethod
5565
 
    def get_client(env):
5566
 
        return EnvJujuClient24(env, '1.24-fake', 'fake-juju-path')
 
704
class TestTempJujuEnv(TestCase):
5567
705
 
5568
706
    def test_no_config_mangling_side_effect(self):
5569
707
        env = SimpleEnvironment('qux', {'type': 'local'})
5570
 
        client = self.get_client(env)
 
708
        client = EnvJujuClient.by_version(env)
5571
709
        with bootstrap_context(client) as fake_home:
5572
710
            with temp_bootstrap_env(fake_home, client):
5573
 
                stub_bootstrap(client)
 
711
                stub_bootstrap()
5574
712
        self.assertEqual(env.config, {'type': 'local'})
5575
713
 
5576
714
    def test_temp_bootstrap_env_environment(self):
5577
715
        env = SimpleEnvironment('qux', {'type': 'local'})
5578
 
        with bootstrap_context() as fake_home:
5579
 
            client = self.get_client(env)
5580
 
            agent_version = client.get_matching_agent_version()
 
716
        client = EnvJujuClient.by_version(env)
 
717
        agent_version = client.get_matching_agent_version()
 
718
        with bootstrap_context(client) as fake_home:
5581
719
            with temp_bootstrap_env(fake_home, client):
5582
720
                temp_home = os.environ['JUJU_HOME']
5583
 
                self.assertEqual(temp_home, os.environ['JUJU_DATA'])
5584
721
                self.assertNotEqual(temp_home, fake_home)
5585
722
                symlink_path = get_jenv_path(fake_home, 'qux')
5586
723
                symlink_target = os.path.realpath(symlink_path)
5587
 
                expected_target = os.path.realpath(
5588
 
                    get_jenv_path(temp_home, 'qux'))
 
724
                expected_target = get_jenv_path(temp_home, 'qux')
5589
725
                self.assertEqual(symlink_target, expected_target)
5590
726
                config = yaml.safe_load(
5591
727
                    open(get_environments_path(temp_home)))
5594
730
                    'root-dir': get_local_root(fake_home, client.env),
5595
731
                    'agent-version': agent_version,
5596
732
                    'test-mode': True,
5597
 
                    'name': 'qux',
5598
 
                }}})
5599
 
                stub_bootstrap(client)
5600
 
 
5601
 
    def test_temp_bootstrap_env_provides_dir(self):
5602
 
        env = SimpleEnvironment('qux', {'type': 'local'})
5603
 
        client = self.get_client(env)
5604
 
        juju_home = os.path.join(self.home_dir, 'asdf')
5605
 
 
5606
 
        def side_effect(*args, **kwargs):
5607
 
            os.mkdir(juju_home)
5608
 
            return juju_home
5609
 
 
5610
 
        with patch('utility.mkdtemp', side_effect=side_effect):
5611
 
            with patch('jujupy.check_free_disk_space', autospec=True):
5612
 
                with temp_bootstrap_env(self.home_dir, client) as temp_home:
5613
 
                    pass
5614
 
        self.assertEqual(temp_home, juju_home)
5615
 
 
5616
 
    def test_temp_bootstrap_env_no_set_home(self):
5617
 
        env = SimpleEnvironment('qux', {'type': 'local'})
5618
 
        client = self.get_client(env)
5619
 
        os.environ['JUJU_HOME'] = 'foo'
5620
 
        os.environ['JUJU_DATA'] = 'bar'
5621
 
        with patch('jujupy.check_free_disk_space', autospec=True):
5622
 
            with temp_bootstrap_env(self.home_dir, client, set_home=False):
5623
 
                self.assertEqual(os.environ['JUJU_HOME'], 'foo')
5624
 
                self.assertEqual(os.environ['JUJU_DATA'], 'bar')
 
733
                    }}})
 
734
                stub_bootstrap()
5625
735
 
5626
736
    def test_output(self):
5627
737
        env = SimpleEnvironment('qux', {'type': 'local'})
5628
 
        client = self.get_client(env)
 
738
        client = EnvJujuClient.by_version(env)
5629
739
        with bootstrap_context(client) as fake_home:
5630
740
            with temp_bootstrap_env(fake_home, client):
5631
 
                stub_bootstrap(client)
 
741
                stub_bootstrap()
5632
742
            jenv_path = get_jenv_path(fake_home, 'qux')
5633
743
            self.assertFalse(os.path.islink(jenv_path))
5634
744
            self.assertEqual(open(jenv_path).read(), 'Bogus jenv')
5635
745
 
5636
746
    def test_rename_on_exception(self):
5637
747
        env = SimpleEnvironment('qux', {'type': 'local'})
5638
 
        client = self.get_client(env)
 
748
        client = EnvJujuClient.by_version(env)
5639
749
        with bootstrap_context(client) as fake_home:
5640
750
            with self.assertRaisesRegexp(Exception, 'test-rename'):
5641
751
                with temp_bootstrap_env(fake_home, client):
5642
 
                    stub_bootstrap(client)
 
752
                    stub_bootstrap()
5643
753
                    raise Exception('test-rename')
5644
754
            jenv_path = get_jenv_path(os.environ['JUJU_HOME'], 'qux')
5645
755
            self.assertFalse(os.path.islink(jenv_path))
5647
757
 
5648
758
    def test_exception_no_jenv(self):
5649
759
        env = SimpleEnvironment('qux', {'type': 'local'})
5650
 
        client = self.get_client(env)
 
760
        client = EnvJujuClient.by_version(env)
5651
761
        with bootstrap_context(client) as fake_home:
5652
762
            with self.assertRaisesRegexp(Exception, 'test-rename'):
5653
763
                with temp_bootstrap_env(fake_home, client):
5659
769
 
5660
770
    def test_check_space_local_lxc(self):
5661
771
        env = SimpleEnvironment('qux', {'type': 'local'})
5662
 
        with bootstrap_context() as fake_home:
5663
 
            client = self.get_client(env)
 
772
        client = EnvJujuClient.by_version(env)
 
773
        with bootstrap_context(client) as fake_home:
5664
774
            with patch('jujupy.check_free_disk_space') as mock_cfds:
5665
775
                with temp_bootstrap_env(fake_home, client):
5666
 
                    stub_bootstrap(client)
 
776
                    stub_bootstrap()
5667
777
        self.assertEqual(mock_cfds.mock_calls, [
5668
778
            call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),
5669
779
            call('/var/lib/lxc', 2000000, 'LXC containers'),
5670
 
        ])
 
780
            ])
5671
781
 
5672
782
    def test_check_space_local_kvm(self):
5673
783
        env = SimpleEnvironment('qux', {'type': 'local', 'container': 'kvm'})
5674
 
        with bootstrap_context() as fake_home:
5675
 
            client = self.get_client(env)
 
784
        client = EnvJujuClient.by_version(env)
 
785
        with bootstrap_context(client) as fake_home:
5676
786
            with patch('jujupy.check_free_disk_space') as mock_cfds:
5677
787
                with temp_bootstrap_env(fake_home, client):
5678
 
                    stub_bootstrap(client)
 
788
                    stub_bootstrap()
5679
789
        self.assertEqual(mock_cfds.mock_calls, [
5680
790
            call(os.path.join(fake_home, 'qux'), 8000000, 'MongoDB files'),
5681
791
            call('/var/lib/uvtool/libvirt/images', 2000000, 'KVM disk files'),
5682
 
        ])
 
792
            ])
5683
793
 
5684
794
    def test_error_on_jenv(self):
5685
795
        env = SimpleEnvironment('qux', {'type': 'local'})
5686
 
        client = self.get_client(env)
 
796
        client = EnvJujuClient.by_version(env)
5687
797
        with bootstrap_context(client) as fake_home:
5688
798
            jenv_path = get_jenv_path(fake_home, 'qux')
5689
799
            os.mkdir(os.path.dirname(jenv_path))
5691
801
                f.write('In the way')
5692
802
            with self.assertRaisesRegexp(Exception, '.* already exists!'):
5693
803
                with temp_bootstrap_env(fake_home, client):
5694
 
                    stub_bootstrap(client)
5695
 
 
5696
 
    def test_not_permanent(self):
5697
 
        env = SimpleEnvironment('qux', {'type': 'local'})
5698
 
        client = self.get_client(env)
5699
 
        with bootstrap_context(client) as fake_home:
5700
 
            client.env.juju_home = fake_home
5701
 
            with temp_bootstrap_env(fake_home, client,
5702
 
                                    permanent=False) as tb_home:
5703
 
                stub_bootstrap(client)
5704
 
            self.assertFalse(os.path.exists(tb_home))
5705
 
            self.assertTrue(os.path.exists(get_jenv_path(fake_home,
5706
 
                            client.env.environment)))
5707
 
            self.assertFalse(os.path.exists(get_jenv_path(tb_home,
5708
 
                             client.env.environment)))
5709
 
        self.assertFalse(os.path.exists(tb_home))
5710
 
        self.assertEqual(client.env.juju_home, fake_home)
5711
 
        self.assertNotEqual(tb_home,
5712
 
                            jes_home_path(fake_home, client.env.environment))
5713
 
 
5714
 
    def test_permanent(self):
5715
 
        env = SimpleEnvironment('qux', {'type': 'local'})
5716
 
        client = self.get_client(env)
5717
 
        with bootstrap_context(client) as fake_home:
5718
 
            client.env.juju_home = fake_home
5719
 
            with temp_bootstrap_env(fake_home, client,
5720
 
                                    permanent=True) as tb_home:
5721
 
                stub_bootstrap(client)
5722
 
            self.assertTrue(os.path.exists(tb_home))
5723
 
            self.assertFalse(os.path.exists(get_jenv_path(fake_home,
5724
 
                             client.env.environment)))
5725
 
            self.assertTrue(os.path.exists(get_jenv_path(tb_home,
5726
 
                            client.env.environment)))
5727
 
        self.assertFalse(os.path.exists(tb_home))
5728
 
        self.assertEqual(client.env.juju_home, tb_home)
5729
 
 
5730
 
 
5731
 
class TestStatus(FakeHomeTestCase):
5732
 
 
5733
 
    def test_iter_machines_no_containers(self):
5734
 
        status = Status({
5735
 
            'machines': {
5736
 
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5737
 
            },
5738
 
            'applications': {}}, '')
5739
 
        self.assertEqual(list(status.iter_machines()),
5740
 
                         [('1', status.status['machines']['1'])])
5741
 
 
5742
 
    def test_iter_machines_containers(self):
5743
 
        status = Status({
5744
 
            'machines': {
5745
 
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5746
 
            },
5747
 
            'applications': {}}, '')
5748
 
        self.assertEqual(list(status.iter_machines(containers=True)), [
5749
 
            ('1', status.status['machines']['1']),
5750
 
            ('1/lxc/0', {'baz': 'qux'}),
5751
 
        ])
 
804
                    stub_bootstrap()
 
805
 
 
806
 
 
807
class TestJujuClientDevel(TestCase):
 
808
 
 
809
    def test_get_version(self):
 
810
        value = ' 5.6 \n'
 
811
        with patch('subprocess.check_output', return_value=value) as vsn:
 
812
            version = JujuClientDevel.get_version()
 
813
        self.assertEqual('5.6', version)
 
814
        vsn.assert_called_with(('juju', '--version'))
 
815
 
 
816
    def test_by_version(self):
 
817
        def juju_cmd_iterator():
 
818
            yield '1.17'
 
819
            yield '1.16'
 
820
            yield '1.16.1'
 
821
            yield '1.15'
 
822
 
 
823
        context = patch.object(
 
824
            JujuClientDevel, 'get_version',
 
825
            side_effect=juju_cmd_iterator().next)
 
826
        with context:
 
827
            self.assertIs(JujuClientDevel,
 
828
                          type(JujuClientDevel.by_version()))
 
829
            with self.assertRaisesRegexp(Exception, 'Unsupported juju: 1.16'):
 
830
                JujuClientDevel.by_version()
 
831
            with self.assertRaisesRegexp(Exception,
 
832
                                         'Unsupported juju: 1.16.1'):
 
833
                JujuClientDevel.by_version()
 
834
            client = JujuClientDevel.by_version()
 
835
            self.assertIs(JujuClientDevel, type(client))
 
836
            self.assertEqual('1.15', client.version)
 
837
 
 
838
    def test_bootstrap_hpcloud(self):
 
839
        env = Environment('hp', '')
 
840
        with patch.object(env, 'hpcloud', lambda: True):
 
841
            with patch.object(EnvJujuClient, 'juju') as mock:
 
842
                JujuClientDevel(None, None).bootstrap(env)
 
843
            mock.assert_called_with(
 
844
                'bootstrap', ('--constraints', 'mem=2G'), False)
 
845
 
 
846
    def test_bootstrap_non_sudo(self):
 
847
        env = Environment('foo', '')
 
848
        with patch.object(SimpleEnvironment, 'needs_sudo', return_value=False):
 
849
            with patch.object(EnvJujuClient, 'juju') as mock:
 
850
                JujuClientDevel(None, None).bootstrap(env)
 
851
            mock.assert_called_with(
 
852
                'bootstrap', ('--constraints', 'mem=2G'), False)
 
853
 
 
854
    def test_bootstrap_sudo(self):
 
855
        env = Environment('foo', '')
 
856
        client = JujuClientDevel(None, None)
 
857
        with patch.object(SimpleEnvironment, 'needs_sudo', return_value=True):
 
858
            with patch.object(EnvJujuClient, 'juju') as mock:
 
859
                client.bootstrap(env)
 
860
            mock.assert_called_with(
 
861
                'bootstrap', ('--constraints', 'mem=2G'), True)
 
862
 
 
863
    def test_destroy_environment_non_sudo(self):
 
864
        env = Environment('foo', '')
 
865
        client = JujuClientDevel(None, None)
 
866
        with patch.object(SimpleEnvironment, 'needs_sudo', return_value=False):
 
867
            with patch.object(EnvJujuClient, 'juju') as mock:
 
868
                client.destroy_environment(env)
 
869
            mock.assert_called_with(
 
870
                'destroy-environment', ('foo', '--force', '-y'),
 
871
                False, check=False, include_e=False, timeout=600)
 
872
 
 
873
    def test_destroy_environment_sudo(self):
 
874
        env = Environment('foo', '')
 
875
        client = JujuClientDevel(None, None)
 
876
        with patch.object(SimpleEnvironment, 'needs_sudo', return_value=True):
 
877
            with patch.object(EnvJujuClient, 'juju') as mock:
 
878
                client.destroy_environment(env)
 
879
            mock.assert_called_with(
 
880
                'destroy-environment', ('foo', '--force', '-y'),
 
881
                True, check=False, include_e=False, timeout=600.0)
 
882
 
 
883
    def test_get_juju_output(self):
 
884
        env = Environment('foo', '')
 
885
        asdf = lambda x, stderr, env: 'asdf'
 
886
        client = JujuClientDevel(None, None)
 
887
        with patch('subprocess.check_output', side_effect=asdf) as mock:
 
888
            result = client.get_juju_output(env, 'bar')
 
889
        self.assertEqual('asdf', result)
 
890
        self.assertEqual((('juju', '--show-log', 'bar', '-e', 'foo'),),
 
891
                         mock.call_args[0])
 
892
 
 
893
    def test_get_juju_output_accepts_varargs(self):
 
894
        env = Environment('foo', '')
 
895
        asdf = lambda x, stderr, env: 'asdf'
 
896
        client = JujuClientDevel(None, None)
 
897
        with patch('subprocess.check_output', side_effect=asdf) as mock:
 
898
            result = client.get_juju_output(env, 'bar', 'baz', '--qux')
 
899
        self.assertEqual('asdf', result)
 
900
        self.assertEqual((('juju', '--show-log', 'bar', '-e', 'foo', 'baz',
 
901
                           '--qux'),), mock.call_args[0])
 
902
 
 
903
    def test_get_juju_output_stderr(self):
 
904
        def raise_without_stderr(args, stderr, env):
 
905
            stderr.write('Hello!')
 
906
            raise subprocess.CalledProcessError('a', 'b')
 
907
        env = Environment('foo', '')
 
908
        client = JujuClientDevel(None, None)
 
909
        with self.assertRaises(subprocess.CalledProcessError) as exc:
 
910
            with patch('subprocess.check_output', raise_without_stderr):
 
911
                client.get_juju_output(env, 'bar')
 
912
        self.assertEqual(exc.exception.stderr, 'Hello!')
 
913
 
 
914
    def test_get_juju_output_accepts_timeout(self):
 
915
        env = Environment('foo', '')
 
916
        client = JujuClientDevel(None, None)
 
917
        with patch('subprocess.check_output') as sco_mock:
 
918
            client.get_juju_output(env, 'bar', timeout=5)
 
919
        self.assertEqual(
 
920
            sco_mock.call_args[0][0],
 
921
            ('timeout', '5.00s', 'juju', '--show-log', 'bar', '-e', 'foo'))
 
922
 
 
923
    def test_juju_output_supplies_path(self):
 
924
        env = Environment('foo', '')
 
925
        client = JujuClientDevel(None, '/foobar/bar')
 
926
        with patch('subprocess.check_output') as sco_mock:
 
927
            client.get_juju_output(env, 'baz')
 
928
        self.assertRegexpMatches(sco_mock.call_args[1]['env']['PATH'],
 
929
                                 r'/foobar\:')
 
930
 
 
931
    def test_get_status(self):
 
932
        output_text = yield dedent("""\
 
933
                - a
 
934
                - b
 
935
                - c
 
936
                """)
 
937
        client = JujuClientDevel(None, None)
 
938
        env = Environment('foo', '')
 
939
        with patch.object(EnvJujuClient, 'get_juju_output',
 
940
                          return_value=output_text):
 
941
            result = client.get_status(env)
 
942
        self.assertEqual(Status, type(result))
 
943
        self.assertEqual(['a', 'b', 'c'], result.status)
 
944
 
 
945
    def test_get_status_retries_on_error(self):
 
946
        client = JujuClientDevel(None, None)
 
947
        client.attempt = 0
 
948
 
 
949
        def get_juju_output(command, *args):
 
950
            if client.attempt == 1:
 
951
                return '"hello"'
 
952
            client.attempt += 1
 
953
            raise subprocess.CalledProcessError(1, command)
 
954
 
 
955
        env = Environment('foo', '')
 
956
        with patch.object(EnvJujuClient, 'get_juju_output', get_juju_output):
 
957
            client.get_status(env)
 
958
 
 
959
    def test_get_status_raises_on_timeout_1(self):
 
960
        client = JujuClientDevel(None, None)
 
961
 
 
962
        def get_juju_output(command):
 
963
            raise subprocess.CalledProcessError(1, command)
 
964
 
 
965
        env = Environment('foo', '')
 
966
        with patch.object(EnvJujuClient, 'get_juju_output',
 
967
                          side_effect=get_juju_output):
 
968
            with patch('jujupy.until_timeout', lambda x: iter([None, None])):
 
969
                with self.assertRaisesRegexp(
 
970
                        Exception, 'Timed out waiting for juju status'):
 
971
                    client.get_status(env)
 
972
 
 
973
    def test_get_status_raises_on_timeout_2(self):
 
974
        client = JujuClientDevel(None, None)
 
975
        env = Environment('foo', '')
 
976
        with patch('jujupy.until_timeout', return_value=iter([1])) as mock_ut:
 
977
            with patch.object(EnvJujuClient, 'get_juju_output',
 
978
                              side_effect=StopIteration):
 
979
                with self.assertRaises(StopIteration):
 
980
                    client.get_status(env, 500)
 
981
        mock_ut.assert_called_with(500)
 
982
 
 
983
    def test_get_env_option(self):
 
984
        client = JujuClientDevel(None, None)
 
985
        env = Environment('foo', '')
 
986
        with patch('subprocess.check_output') as mock:
 
987
            mock.return_value = 'https://example.org/juju/tools'
 
988
            result = client.get_env_option(env, 'tools-metadata-url')
 
989
        self.assertEqual(
 
990
            mock.call_args[0][0],
 
991
            ('juju', '--show-log', 'get-env', '-e', 'foo',
 
992
             'tools-metadata-url'))
 
993
        self.assertEqual('https://example.org/juju/tools', result)
 
994
 
 
995
    def test_set_env_option(self):
 
996
        client = JujuClientDevel(None, None)
 
997
        env = Environment('foo', '')
 
998
        with patch('subprocess.check_call') as mock:
 
999
            client.set_env_option(
 
1000
                env, 'tools-metadata-url', 'https://example.org/juju/tools')
 
1001
        mock.assert_called_with(
 
1002
            ('juju', '--show-log', 'set-env', '-e', 'foo',
 
1003
             'tools-metadata-url=https://example.org/juju/tools'),
 
1004
            env=os.environ)
 
1005
 
 
1006
    def test_juju(self):
 
1007
        env = Environment('qux', '')
 
1008
        client = JujuClientDevel(None, None)
 
1009
        with patch('sys.stdout') as stdout_mock:
 
1010
            with patch('subprocess.check_call') as mock:
 
1011
                client.juju(env, 'foo', ('bar', 'baz'))
 
1012
        mock.assert_called_with(('juju', '--show-log', 'foo', '-e', 'qux',
 
1013
                                 'bar', 'baz'), env=os.environ)
 
1014
        stdout_mock.flush.assert_called_with()
 
1015
 
 
1016
    def test_juju_env(self):
 
1017
        env = Environment('qux', '')
 
1018
        client = JujuClientDevel(None, '/foobar/baz')
 
1019
        with patch('subprocess.check_call') as cc_mock:
 
1020
            client.juju(env, 'foo', ('bar', 'baz'))
 
1021
        self.assertRegexpMatches(cc_mock.call_args[1]['env']['PATH'],
 
1022
                                 r'/foobar\:')
 
1023
 
 
1024
    def test_juju_no_check(self):
 
1025
        env = Environment('qux', '')
 
1026
        client = JujuClientDevel(None, None)
 
1027
        with patch('sys.stdout') as stdout_mock:
 
1028
            with patch('subprocess.call') as mock:
 
1029
                client.juju(env, 'foo', ('bar', 'baz'), check=False)
 
1030
        mock.assert_called_with(('juju', '--show-log', 'foo', '-e', 'qux',
 
1031
                                 'bar', 'baz'), env=os.environ)
 
1032
        stdout_mock.flush.assert_called_with()
 
1033
 
 
1034
    def test_juju_no_check_env(self):
 
1035
        env = Environment('qux', '')
 
1036
        client = JujuClientDevel(None, '/foobar/baz')
 
1037
        with patch('subprocess.call') as call_mock:
 
1038
            client.juju(env, 'foo', ('bar', 'baz'), check=False)
 
1039
        self.assertRegexpMatches(call_mock.call_args[1]['env']['PATH'],
 
1040
                                 r'/foobar\:')
 
1041
 
 
1042
 
 
1043
class TestStatus(TestCase):
5752
1044
 
5753
1045
    def test_agent_items_empty(self):
5754
 
        status = Status({'machines': {}, 'applications': {}}, '')
 
1046
        status = Status({'machines': {}, 'services': {}}, '')
5755
1047
        self.assertItemsEqual([], status.agent_items())
5756
1048
 
5757
1049
    def test_agent_items(self):
5759
1051
            'machines': {
5760
1052
                '1': {'foo': 'bar'}
5761
1053
            },
5762
 
            'applications': {
 
1054
            'services': {
5763
1055
                'jenkins': {
5764
1056
                    'units': {
5765
 
                        'jenkins/1': {
5766
 
                            'subordinates': {
5767
 
                                'sub': {'baz': 'qux'}
5768
 
                            }
5769
 
                        }
 
1057
                        'jenkins/1': {'baz': 'qux'}
5770
1058
                    }
5771
1059
                }
5772
1060
            }
5773
1061
        }, '')
5774
1062
        expected = [
5775
 
            ('1', {'foo': 'bar'}),
5776
 
            ('jenkins/1', {'subordinates': {'sub': {'baz': 'qux'}}}),
5777
 
            ('sub', {'baz': 'qux'})]
 
1063
            ('1', {'foo': 'bar'}), ('jenkins/1', {'baz': 'qux'})]
5778
1064
        self.assertItemsEqual(expected, status.agent_items())
5779
1065
 
5780
1066
    def test_agent_items_containers(self):
5784
1070
                    '2': {'qux': 'baz'},
5785
1071
                }}
5786
1072
            },
5787
 
            'applications': {}
 
1073
            'services': {}
5788
1074
        }, '')
5789
1075
        expected = [
5790
1076
            ('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
5792
1078
        ]
5793
1079
        self.assertItemsEqual(expected, status.agent_items())
5794
1080
 
5795
 
    def test_get_service_count_zero(self):
5796
 
        status = Status({
5797
 
            'machines': {
5798
 
                '1': {'agent-state': 'good'},
5799
 
                '2': {},
5800
 
            },
5801
 
        }, '')
5802
 
        self.assertEqual(0, status.get_service_count())
5803
 
 
5804
 
    def test_get_service_count(self):
5805
 
        status = Status({
5806
 
            'machines': {
5807
 
                '1': {'agent-state': 'good'},
5808
 
                '2': {},
5809
 
            },
5810
 
            'applications': {
5811
 
                'jenkins': {
5812
 
                    'units': {
5813
 
                        'jenkins/1': {'agent-state': 'bad'},
5814
 
                    }
5815
 
                },
5816
 
                'dummy-sink': {
5817
 
                    'units': {
5818
 
                        'dummy-sink/0': {'agent-state': 'started'},
5819
 
                    }
5820
 
                },
5821
 
                'juju-reports': {
5822
 
                    'units': {
5823
 
                        'juju-reports/0': {'agent-state': 'pending'},
5824
 
                    }
5825
 
                }
5826
 
            }
5827
 
        }, '')
5828
 
        self.assertEqual(3, status.get_service_count())
5829
 
 
5830
 
    def test_get_service_unit_count_zero(self):
5831
 
        status = Status({
5832
 
            'machines': {
5833
 
                '1': {'agent-state': 'good'},
5834
 
                '2': {},
5835
 
            },
5836
 
        }, '')
5837
 
        self.assertEqual(0, status.get_service_unit_count('jenkins'))
5838
 
 
5839
 
    def test_get_service_unit_count(self):
5840
 
        status = Status({
5841
 
            'machines': {
5842
 
                '1': {'agent-state': 'good'},
5843
 
                '2': {},
5844
 
            },
5845
 
            'applications': {
5846
 
                'jenkins': {
5847
 
                    'units': {
5848
 
                        'jenkins/1': {'agent-state': 'bad'},
5849
 
                        'jenkins/2': {'agent-state': 'bad'},
5850
 
                        'jenkins/3': {'agent-state': 'bad'},
5851
 
                    }
5852
 
                }
5853
 
            }
5854
 
        }, '')
5855
 
        self.assertEqual(3, status.get_service_unit_count('jenkins'))
5856
 
 
5857
 
    def test_get_unit(self):
5858
 
        status = Status({
5859
 
            'machines': {
5860
 
                '1': {},
5861
 
            },
5862
 
            'applications': {
5863
 
                'jenkins': {
5864
 
                    'units': {
5865
 
                        'jenkins/1': {'agent-state': 'bad'},
5866
 
                    }
5867
 
                },
5868
 
                'dummy-sink': {
5869
 
                    'units': {
5870
 
                        'jenkins/2': {'agent-state': 'started'},
5871
 
                    }
5872
 
                },
5873
 
            }
5874
 
        }, '')
5875
 
        self.assertEqual(
5876
 
            status.get_unit('jenkins/1'), {'agent-state': 'bad'})
5877
 
        self.assertEqual(
5878
 
            status.get_unit('jenkins/2'), {'agent-state': 'started'})
5879
 
        with self.assertRaisesRegexp(KeyError, 'jenkins/3'):
5880
 
            status.get_unit('jenkins/3')
5881
 
 
5882
 
    def test_service_subordinate_units(self):
5883
 
        status = Status({
5884
 
            'machines': {
5885
 
                '1': {},
5886
 
            },
5887
 
            'applications': {
5888
 
                'ubuntu': {},
5889
 
                'jenkins': {
5890
 
                    'units': {
5891
 
                        'jenkins/1': {
5892
 
                            'subordinates': {
5893
 
                                'chaos-monkey/0': {'agent-state': 'started'},
5894
 
                            }
5895
 
                        }
5896
 
                    }
5897
 
                },
5898
 
                'dummy-sink': {
5899
 
                    'units': {
5900
 
                        'jenkins/2': {
5901
 
                            'subordinates': {
5902
 
                                'chaos-monkey/1': {'agent-state': 'started'}
5903
 
                            }
5904
 
                        },
5905
 
                        'jenkins/3': {
5906
 
                            'subordinates': {
5907
 
                                'chaos-monkey/2': {'agent-state': 'started'}
5908
 
                            }
5909
 
                        }
5910
 
                    }
5911
 
                }
5912
 
            }
5913
 
        }, '')
5914
 
        self.assertItemsEqual(
5915
 
            status.service_subordinate_units('ubuntu'),
5916
 
            [])
5917
 
        self.assertItemsEqual(
5918
 
            status.service_subordinate_units('jenkins'),
5919
 
            [('chaos-monkey/0', {'agent-state': 'started'},)])
5920
 
        self.assertItemsEqual(
5921
 
            status.service_subordinate_units('dummy-sink'), [
5922
 
                ('chaos-monkey/1', {'agent-state': 'started'}),
5923
 
                ('chaos-monkey/2', {'agent-state': 'started'})]
5924
 
            )
5925
 
 
5926
 
    def test_get_open_ports(self):
5927
 
        status = Status({
5928
 
            'machines': {
5929
 
                '1': {},
5930
 
            },
5931
 
            'applications': {
5932
 
                'jenkins': {
5933
 
                    'units': {
5934
 
                        'jenkins/1': {'agent-state': 'bad'},
5935
 
                    }
5936
 
                },
5937
 
                'dummy-sink': {
5938
 
                    'units': {
5939
 
                        'jenkins/2': {'open-ports': ['42/tcp']},
5940
 
                    }
5941
 
                },
5942
 
            }
5943
 
        }, '')
5944
 
        self.assertEqual(status.get_open_ports('jenkins/1'), [])
5945
 
        self.assertEqual(status.get_open_ports('jenkins/2'), ['42/tcp'])
5946
 
 
5947
 
    def test_agent_states_with_agent_state(self):
5948
 
        status = Status({
5949
 
            'machines': {
5950
 
                '1': {'agent-state': 'good'},
5951
 
                '2': {},
5952
 
            },
5953
 
            'applications': {
 
1081
    def test_agent_states(self):
 
1082
        status = Status({
 
1083
            'machines': {
 
1084
                '1': {'agent-state': 'good'},
 
1085
                '2': {},
 
1086
            },
 
1087
            'services': {
5954
1088
                'jenkins': {
5955
1089
                    'units': {
5956
1090
                        'jenkins/1': {'agent-state': 'bad'},
5966
1100
        }
5967
1101
        self.assertEqual(expected, status.agent_states())
5968
1102
 
5969
 
    def test_agent_states_with_agent_status(self):
5970
 
        status = Status({
5971
 
            'machines': {
5972
 
                '1': {'agent-state': 'good'},
5973
 
                '2': {},
5974
 
            },
5975
 
            'applications': {
5976
 
                'jenkins': {
5977
 
                    'units': {
5978
 
                        'jenkins/1': {'agent-status': {'current': 'bad'}},
5979
 
                        'jenkins/2': {'agent-status': {'current': 'good'}},
5980
 
                        'jenkins/3': {},
5981
 
                    }
5982
 
                }
5983
 
            }
5984
 
        }, '')
5985
 
        expected = {
5986
 
            'good': ['1', 'jenkins/2'],
5987
 
            'bad': ['jenkins/1'],
5988
 
            'no-agent': ['2', 'jenkins/3'],
5989
 
        }
5990
 
        self.assertEqual(expected, status.agent_states())
5991
 
 
5992
 
    def test_agent_states_with_juju_status(self):
5993
 
        status = Status({
5994
 
            'machines': {
5995
 
                '1': {'juju-status': {'current': 'good'}},
5996
 
                '2': {},
5997
 
            },
5998
 
            'applications': {
5999
 
                'jenkins': {
6000
 
                    'units': {
6001
 
                        'jenkins/1': {'juju-status': {'current': 'bad'}},
6002
 
                        'jenkins/2': {'juju-status': {'current': 'good'}},
6003
 
                        'jenkins/3': {},
6004
 
                    }
6005
 
                }
6006
 
            }
6007
 
        }, '')
6008
 
        expected = {
6009
 
            'good': ['1', 'jenkins/2'],
6010
 
            'bad': ['jenkins/1'],
6011
 
            'no-agent': ['2', 'jenkins/3'],
6012
 
        }
6013
 
        self.assertEqual(expected, status.agent_states())
6014
 
 
6015
1103
    def test_check_agents_started_not_started(self):
6016
1104
        status = Status({
6017
1105
            'machines': {
6018
1106
                '1': {'agent-state': 'good'},
6019
1107
                '2': {},
6020
1108
            },
6021
 
            'applications': {
 
1109
            'services': {
6022
1110
                'jenkins': {
6023
1111
                    'units': {
6024
1112
                        'jenkins/1': {'agent-state': 'bad'},
6030
1118
        self.assertEqual(status.agent_states(),
6031
1119
                         status.check_agents_started('env1'))
6032
1120
 
6033
 
    def test_check_agents_started_all_started_with_agent_state(self):
 
1121
    def test_check_agents_started_all_started(self):
6034
1122
        status = Status({
6035
1123
            'machines': {
6036
1124
                '1': {'agent-state': 'started'},
6037
1125
                '2': {'agent-state': 'started'},
6038
1126
            },
6039
 
            'applications': {
 
1127
            'services': {
6040
1128
                'jenkins': {
6041
1129
                    'units': {
6042
 
                        'jenkins/1': {
6043
 
                            'agent-state': 'started',
6044
 
                            'subordinates': {
6045
 
                                'sub1': {
6046
 
                                    'agent-state': 'started'
6047
 
                                }
6048
 
                            }
6049
 
                        },
 
1130
                        'jenkins/1': {'agent-state': 'started'},
6050
1131
                        'jenkins/2': {'agent-state': 'started'},
6051
1132
                    }
6052
1133
                }
6054
1135
        }, '')
6055
1136
        self.assertIs(None, status.check_agents_started('env1'))
6056
1137
 
6057
 
    def test_check_agents_started_all_started_with_agent_status(self):
6058
 
        status = Status({
6059
 
            'machines': {
6060
 
                '1': {'agent-state': 'started'},
6061
 
                '2': {'agent-state': 'started'},
6062
 
            },
6063
 
            'applications': {
6064
 
                'jenkins': {
6065
 
                    'units': {
6066
 
                        'jenkins/1': {
6067
 
                            'agent-status': {'current': 'idle'},
6068
 
                            'subordinates': {
6069
 
                                'sub1': {
6070
 
                                    'agent-status': {'current': 'idle'}
6071
 
                                }
6072
 
                            }
6073
 
                        },
6074
 
                        'jenkins/2': {'agent-status': {'current': 'idle'}},
6075
 
                    }
6076
 
                }
6077
 
            }
6078
 
        }, '')
6079
 
        self.assertIs(None, status.check_agents_started('env1'))
6080
 
 
6081
1138
    def test_check_agents_started_agent_error(self):
6082
1139
        status = Status({
6083
1140
            'machines': {
6084
1141
                '1': {'agent-state': 'any-error'},
6085
1142
            },
6086
 
            'applications': {}
 
1143
            'services': {}
6087
1144
        }, '')
6088
1145
        with self.assertRaisesRegexp(ErroredUnit,
6089
1146
                                     '1 is in state any-error'):
6090
1147
            status.check_agents_started('env1')
6091
1148
 
6092
 
    def do_check_agents_started_agent_state_info_failure(self, failure):
 
1149
    def do_check_agents_started_failure(self, failure):
6093
1150
        status = Status({
6094
1151
            'machines': {'0': {
6095
1152
                'agent-state-info': failure}},
6096
 
            'applications': {},
6097
 
        }, '')
6098
 
        with self.assertRaises(ErroredUnit) as e_cxt:
6099
 
            status.check_agents_started()
6100
 
        e = e_cxt.exception
6101
 
        self.assertEqual(
6102
 
            str(e), '0 is in state {}'.format(failure))
6103
 
        self.assertEqual(e.unit_name, '0')
6104
 
        self.assertEqual(e.state, failure)
6105
 
 
6106
 
    def do_check_agents_started_juju_status_failure(self, failure):
6107
 
        status = Status({
6108
 
            'machines': {
6109
 
                '0': {
6110
 
                    'juju-status': {
6111
 
                        'current': 'error',
6112
 
                        'message': failure}
6113
 
                    },
6114
 
                }
6115
 
            }, '')
6116
 
        with self.assertRaises(ErroredUnit) as e_cxt:
6117
 
            status.check_agents_started()
6118
 
        e = e_cxt.exception
6119
 
        # if message is blank, the failure should reflect the state instead
6120
 
        if not failure:
6121
 
            failure = 'error'
6122
 
        self.assertEqual(
6123
 
            str(e), '0 is in state {}'.format(failure))
6124
 
        self.assertEqual(e.unit_name, '0')
6125
 
        self.assertEqual(e.state, failure)
6126
 
 
6127
 
    def do_check_agents_started_info_and_status_failure(self, failure):
6128
 
        status = Status({
6129
 
            'machines': {
6130
 
                '0': {
6131
 
                    'agent-state-info': failure,
6132
 
                    'juju-status': {
6133
 
                        'current': 'error',
6134
 
                        'message': failure}
6135
 
                    },
6136
 
                }
6137
 
            }, '')
6138
 
        with self.assertRaises(ErroredUnit) as e_cxt:
6139
 
            status.check_agents_started()
6140
 
        e = e_cxt.exception
6141
 
        self.assertEqual(
6142
 
            str(e), '0 is in state {}'.format(failure))
6143
 
        self.assertEqual(e.unit_name, '0')
6144
 
        self.assertEqual(e.state, failure)
6145
 
 
6146
 
    def test_check_agents_started_read_juju_status_error(self):
6147
 
        failures = ['no "centos7" images in us-east-1 with arches [amd64]',
6148
 
                    'sending new instance request: GCE operation ' +
6149
 
                    '"operation-143" failed', '']
6150
 
        for failure in failures:
6151
 
            self.do_check_agents_started_juju_status_failure(failure)
6152
 
 
6153
 
    def test_check_agents_started_read_agent_state_info_error(self):
6154
 
        failures = ['cannot set up groups foobar', 'cannot run instance',
6155
 
                    'cannot run instances', 'error executing "lxc-start"']
6156
 
        for failure in failures:
6157
 
            self.do_check_agents_started_agent_state_info_failure(failure)
 
1153
            'services': {},
 
1154
            }, '')
 
1155
        with self.assertRaises(ErroredUnit) as e_cxt:
 
1156
            status.check_agents_started()
 
1157
        e = e_cxt.exception
 
1158
        self.assertEqual(
 
1159
            str(e), '0 is in state {}'.format(failure))
 
1160
        self.assertEqual(e.unit_name, '0')
 
1161
        self.assertEqual(e.state, failure)
 
1162
 
 
1163
    def test_check_agents_cannot_set_up_groups(self):
 
1164
        self.do_check_agents_started_failure('cannot set up groups foobar')
 
1165
 
 
1166
    def test_check_agents_error(self):
 
1167
        self.do_check_agents_started_failure('error executing "lxc-start"')
 
1168
 
 
1169
    def test_check_agents_cannot_run_instances(self):
 
1170
        self.do_check_agents_started_failure('cannot run instances')
 
1171
 
 
1172
    def test_check_agents_cannot_run_instance(self):
 
1173
        self.do_check_agents_started_failure('cannot run instance')
6158
1174
 
6159
1175
    def test_check_agents_started_agent_info_error(self):
6160
1176
        # Sometimes the error is indicated in a special 'agent-state-info'
6163
1179
            'machines': {
6164
1180
                '1': {'agent-state-info': 'any-error'},
6165
1181
            },
6166
 
            'applications': {}
 
1182
            'services': {}
6167
1183
        }, '')
6168
1184
        with self.assertRaisesRegexp(ErroredUnit,
6169
1185
                                     '1 is in state any-error'):
6170
1186
            status.check_agents_started('env1')
6171
1187
 
6172
 
    def test_get_agent_versions_1x(self):
 
1188
    def test_get_agent_versions(self):
6173
1189
        status = Status({
6174
1190
            'machines': {
6175
1191
                '1': {'agent-version': '1.6.2'},
6176
1192
                '2': {'agent-version': '1.6.1'},
6177
1193
            },
6178
 
            'applications': {
 
1194
            'services': {
6179
1195
                'jenkins': {
6180
1196
                    'units': {
6181
1197
                        'jenkins/0': {
6191
1207
            'unknown': {'jenkins/1'},
6192
1208
        }, status.get_agent_versions())
6193
1209
 
6194
 
    def test_get_agent_versions_2x(self):
6195
 
        status = Status({
6196
 
            'machines': {
6197
 
                '1': {'juju-status': {'version': '1.6.2'}},
6198
 
                '2': {'juju-status': {'version': '1.6.1'}},
6199
 
            },
6200
 
            'applications': {
6201
 
                'jenkins': {
6202
 
                    'units': {
6203
 
                        'jenkins/0': {
6204
 
                            'juju-status': {'version': '1.6.1'}},
6205
 
                        'jenkins/1': {},
6206
 
                    },
6207
 
                }
6208
 
            }
6209
 
        }, '')
6210
 
        self.assertEqual({
6211
 
            '1.6.2': {'1'},
6212
 
            '1.6.1': {'jenkins/0', '2'},
6213
 
            'unknown': {'jenkins/1'},
6214
 
        }, status.get_agent_versions())
6215
 
 
6216
1210
    def test_iter_new_machines(self):
6217
1211
        old_status = Status({
6218
1212
            'machines': {
6219
1213
                'bar': 'bar_info',
6220
 
            }
6221
 
        }, '')
 
1214
                }
 
1215
            }, '')
6222
1216
        new_status = Status({
6223
1217
            'machines': {
6224
1218
                'foo': 'foo_info',
6225
1219
                'bar': 'bar_info',
6226
 
            }
6227
 
        }, '')
 
1220
                }
 
1221
            }, '')
6228
1222
        self.assertItemsEqual(new_status.iter_new_machines(old_status),
6229
1223
                              [('foo', 'foo_info')])
6230
1224
 
6233
1227
            'machines': {
6234
1228
                '0': {'instance-id': 'foo-bar'},
6235
1229
                '1': {},
6236
 
            }
6237
 
        }, '')
 
1230
                }
 
1231
            }, '')
6238
1232
        self.assertEqual(status.get_instance_id('0'), 'foo-bar')
6239
1233
        with self.assertRaises(KeyError):
6240
1234
            status.get_instance_id('1')
6248
1242
        self.assertEqual(status.status_text, text)
6249
1243
        self.assertEqual(status.status, {
6250
1244
            'machines': {'0': {'agent-state': 'pending'}},
6251
 
            'applications': {'jenkins': {'units': {'jenkins/0': {
 
1245
            'services': {'jenkins': {'units': {'jenkins/0': {
6252
1246
                'agent-state': 'horsefeathers'}}}}
6253
1247
        })
6254
1248
 
6255
 
    def test_iter_units(self):
6256
 
        started_unit = {'agent-state': 'started'}
6257
 
        unit_with_subordinates = {
6258
 
            'agent-state': 'started',
6259
 
            'subordinates': {
6260
 
                'ntp/0': started_unit,
6261
 
                'nrpe/0': started_unit,
6262
 
            },
6263
 
        }
6264
 
        status = Status({
6265
 
            'machines': {
6266
 
                '1': {'agent-state': 'started'},
6267
 
            },
6268
 
            'applications': {
6269
 
                'jenkins': {
6270
 
                    'units': {
6271
 
                        'jenkins/0': unit_with_subordinates,
6272
 
                    }
6273
 
                },
6274
 
                'application': {
6275
 
                    'units': {
6276
 
                        'application/0': started_unit,
6277
 
                        'application/1': started_unit,
6278
 
                    }
6279
 
                },
6280
 
            }
6281
 
        }, '')
6282
 
        expected = [
6283
 
            ('application/0', started_unit),
6284
 
            ('application/1', started_unit),
6285
 
            ('jenkins/0', unit_with_subordinates),
6286
 
            ('nrpe/0', started_unit),
6287
 
            ('ntp/0', started_unit),
6288
 
        ]
6289
 
        gen = status.iter_units()
6290
 
        self.assertIsInstance(gen, types.GeneratorType)
6291
 
        self.assertEqual(expected, list(gen))
6292
 
 
6293
 
    def test_get_applications_gets_applications(self):
6294
 
        status = Status({
6295
 
            'services': {'service': {}},
6296
 
            'applications': {'application': {}},
6297
 
            }, '')
6298
 
        self.assertEqual({'application': {}}, status.get_applications())
6299
 
 
6300
 
 
6301
 
class TestServiceStatus(FakeHomeTestCase):
6302
 
 
6303
 
    def test_get_applications_gets_services(self):
6304
 
        status = ServiceStatus({
6305
 
            'services': {'service': {}},
6306
 
            'applications': {'application': {}},
6307
 
            }, '')
6308
 
        self.assertEqual({'service': {}}, status.get_applications())
6309
 
 
6310
1249
 
6311
1250
def fast_timeout(count):
6312
1251
    if False:
6315
1254
 
6316
1255
@contextmanager
6317
1256
def temp_config():
6318
 
    with temp_dir() as home:
 
1257
    home = tempfile.mkdtemp()
 
1258
    try:
 
1259
        environments_path = os.path.join(home, 'environments.yaml')
 
1260
        old_home = os.environ.get('JUJU_HOME')
6319
1261
        os.environ['JUJU_HOME'] = home
6320
 
        environments_path = os.path.join(home, 'environments.yaml')
6321
 
        with open(environments_path, 'w') as environments:
6322
 
            yaml.dump({'environments': {
6323
 
                'foo': {'type': 'local'}
6324
 
            }}, environments)
6325
 
        yield
6326
 
 
6327
 
 
6328
 
class TestController(TestCase):
6329
 
 
6330
 
    def test_controller(self):
6331
 
        controller = Controller('ctrl')
6332
 
        self.assertEqual('ctrl', controller.name)
 
1262
        try:
 
1263
            with open(environments_path, 'w') as environments:
 
1264
                yaml.dump({'environments': {
 
1265
                    'foo': {'type': 'local'}
 
1266
                }}, environments)
 
1267
            yield
 
1268
        finally:
 
1269
            if old_home is None:
 
1270
                del os.environ['JUJU_HOME']
 
1271
            else:
 
1272
                os.environ['JUJU_HOME'] = old_home
 
1273
    finally:
 
1274
        shutil.rmtree(home)
6333
1275
 
6334
1276
 
6335
1277
class TestSimpleEnvironment(TestCase):
6336
1278
 
6337
 
    def test_default_controller(self):
6338
 
        default = SimpleEnvironment('foo')
6339
 
        self.assertEqual('foo', default.controller.name)
6340
 
 
6341
 
    def test_clone(self):
6342
 
        orig = SimpleEnvironment('foo', {'type': 'bar'}, 'myhome')
6343
 
        orig.local = 'local1'
6344
 
        orig.kvm = 'kvm1'
6345
 
        orig.maas = 'maas1'
6346
 
        orig.joyent = 'joyent1'
6347
 
        copy = orig.clone()
6348
 
        self.assertIs(SimpleEnvironment, type(copy))
6349
 
        self.assertIsNot(orig, copy)
6350
 
        self.assertEqual(copy.environment, 'foo')
6351
 
        self.assertIsNot(orig.config, copy.config)
6352
 
        self.assertEqual({'type': 'bar'}, copy.config)
6353
 
        self.assertEqual('myhome', copy.juju_home)
6354
 
        self.assertEqual('local1', copy.local)
6355
 
        self.assertEqual('kvm1', copy.kvm)
6356
 
        self.assertEqual('maas1', copy.maas)
6357
 
        self.assertEqual('joyent1', copy.joyent)
6358
 
        self.assertIs(orig.controller, copy.controller)
6359
 
 
6360
 
    def test_clone_model_name(self):
6361
 
        orig = SimpleEnvironment('foo', {'type': 'bar', 'name': 'oldname'},
6362
 
                                 'myhome')
6363
 
        copy = orig.clone(model_name='newname')
6364
 
        self.assertEqual('newname', copy.environment)
6365
 
        self.assertEqual('newname', copy.config['name'])
6366
 
 
6367
 
    def test_set_model_name(self):
6368
 
        env = SimpleEnvironment('foo', {})
6369
 
        env.set_model_name('bar')
6370
 
        self.assertEqual(env.environment, 'bar')
6371
 
        self.assertEqual(env.controller.name, 'bar')
6372
 
        self.assertEqual(env.config['name'], 'bar')
6373
 
 
6374
 
    def test_set_model_name_not_controller(self):
6375
 
        env = SimpleEnvironment('foo', {})
6376
 
        env.set_model_name('bar', set_controller=False)
6377
 
        self.assertEqual(env.environment, 'bar')
6378
 
        self.assertEqual(env.controller.name, 'foo')
6379
 
        self.assertEqual(env.config['name'], 'bar')
6380
 
 
6381
1279
    def test_local_from_config(self):
6382
1280
        env = SimpleEnvironment('local', {'type': 'openstack'})
6383
1281
        self.assertFalse(env.local, 'Does not respect config type.')
6391
1289
                                {'type': 'local', 'container': 'kvm'})
6392
1290
        self.assertTrue(env.kvm, 'Does not respect config type.')
6393
1291
 
 
1292
    def test_hpcloud_from_config(self):
 
1293
        env = SimpleEnvironment('cloud', {'auth-url': 'before.keystone.after'})
 
1294
        self.assertFalse(env.hpcloud, 'Does not respect config type.')
 
1295
        env = SimpleEnvironment('hp', {'auth-url': 'before.hpcloudsvc.after/'})
 
1296
        self.assertTrue(env.hpcloud, 'Does not respect config type.')
 
1297
 
6394
1298
    def test_from_config(self):
6395
1299
        with temp_config():
6396
1300
            env = SimpleEnvironment.from_config('foo')
6402
1306
            with self.assertRaises(NoSuchEnvironment):
6403
1307
                SimpleEnvironment.from_config('bar')
6404
1308
 
6405
 
    def test_from_config_none(self):
 
1309
 
 
1310
class TestEnvironment(TestCase):
 
1311
 
 
1312
    @staticmethod
 
1313
    def make_status_yaml(key, machine_value, unit_value):
 
1314
        return dedent("""\
 
1315
            machines:
 
1316
              "0":
 
1317
                {0}: {1}
 
1318
            services:
 
1319
              jenkins:
 
1320
                units:
 
1321
                  jenkins/0:
 
1322
                    {0}: {2}
 
1323
        """.format(key, machine_value, unit_value))
 
1324
 
 
1325
    def test_wait_for_started(self):
 
1326
        value = self.make_status_yaml('agent-state', 'started', 'started')
 
1327
        env = Environment('local', JujuClientDevel(None, None))
 
1328
        with patch.object(EnvJujuClient, 'get_juju_output',
 
1329
                          return_value=value):
 
1330
            env.wait_for_started()
 
1331
 
 
1332
    def test_wait_for_started_timeout(self):
 
1333
        value = self.make_status_yaml('agent-state', 'pending', 'started')
 
1334
        env = Environment('local', JujuClientDevel(None, None))
 
1335
        with patch('jujupy.until_timeout', lambda x, start=None: range(1)):
 
1336
            with patch.object(EnvJujuClient, 'get_juju_output',
 
1337
                              return_value=value):
 
1338
                with self.assertRaisesRegexp(
 
1339
                        Exception,
 
1340
                        'Timed out waiting for agents to start in local'):
 
1341
                    with patch('logging.error'):
 
1342
                        env.wait_for_started()
 
1343
 
 
1344
    def test_wait_for_version(self):
 
1345
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
 
1346
        env = Environment('local', JujuClientDevel(None, None))
 
1347
        with patch.object(
 
1348
                EnvJujuClient, 'get_juju_output', return_value=value):
 
1349
            env.wait_for_version('1.17.2')
 
1350
 
 
1351
    def test_wait_for_version_timeout(self):
 
1352
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.1')
 
1353
        env = Environment('local', JujuClientDevel(None, None))
 
1354
        with patch('jujupy.until_timeout', lambda x: range(0)):
 
1355
            with patch.object(EnvJujuClient, 'get_juju_output',
 
1356
                              return_value=value):
 
1357
                with self.assertRaisesRegexp(
 
1358
                        Exception, 'Some versions did not update'):
 
1359
                    env.wait_for_version('1.17.2')
 
1360
 
 
1361
    def test_wait_for_version_handles_connection_error(self):
 
1362
        err = subprocess.CalledProcessError(2, 'foo')
 
1363
        err.stderr = 'Unable to connect to environment'
 
1364
        err = CannotConnectEnv(err)
 
1365
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
 
1366
        actions = [err, status]
 
1367
 
 
1368
        def get_juju_output_fake(*args):
 
1369
            action = actions.pop(0)
 
1370
            if isinstance(action, Exception):
 
1371
                raise action
 
1372
            else:
 
1373
                return action
 
1374
 
 
1375
        env = Environment('local', JujuClientDevel(None, None))
 
1376
        output_real = 'test_jujupy.EnvJujuClient.get_juju_output'
 
1377
        devnull = open(os.devnull, 'w')
 
1378
        with patch('sys.stdout', devnull):
 
1379
            with patch(output_real, get_juju_output_fake):
 
1380
                env.wait_for_version('1.17.2')
 
1381
 
 
1382
    def test_wait_for_version_raises_non_connection_error(self):
 
1383
        err = Exception('foo')
 
1384
        status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
 
1385
        actions = [err, status]
 
1386
 
 
1387
        def get_juju_output_fake(*args):
 
1388
            action = actions.pop(0)
 
1389
            if isinstance(action, Exception):
 
1390
                raise action
 
1391
            else:
 
1392
                return action
 
1393
 
 
1394
        env = Environment('local', JujuClientDevel(None, None))
 
1395
        output_real = 'test_jujupy.EnvJujuClient.get_juju_output'
 
1396
        devnull = open(os.devnull, 'w')
 
1397
        with patch('sys.stdout', devnull):
 
1398
            with patch(output_real, get_juju_output_fake):
 
1399
                with self.assertRaisesRegexp(Exception, 'foo'):
 
1400
                    env.wait_for_version('1.17.2')
 
1401
 
 
1402
    def test_from_config(self):
6406
1403
        with temp_config():
6407
 
            os.environ['JUJU_ENV'] = 'foo'
6408
 
            # GZ 2015-10-15: Currently default_env calls the juju on path here.
6409
 
            with patch('jujuconfig.default_env', autospec=True,
6410
 
                       return_value='foo') as cde_mock:
6411
 
                env = SimpleEnvironment.from_config(None)
6412
 
            self.assertEqual(env.environment, 'foo')
6413
 
            cde_mock.assert_called_once_with()
6414
 
 
6415
 
    def test_juju_home(self):
6416
 
        env = SimpleEnvironment('foo')
6417
 
        self.assertIs(None, env.juju_home)
6418
 
        env = SimpleEnvironment('foo', juju_home='baz')
6419
 
        self.assertEqual('baz', env.juju_home)
6420
 
 
6421
 
    def test_make_jes_home(self):
6422
 
        with temp_dir() as juju_home:
6423
 
            with SimpleEnvironment('foo').make_jes_home(
6424
 
                    juju_home, 'bar', {'baz': 'qux'}) as jes_home:
6425
 
                pass
6426
 
            with open(get_environments_path(jes_home)) as env_file:
6427
 
                env = yaml.safe_load(env_file)
6428
 
        self.assertEqual(env, {'baz': 'qux'})
6429
 
        self.assertEqual(jes_home, jes_home_path(juju_home, 'bar'))
6430
 
 
6431
 
    def test_make_jes_home_clean_existing(self):
6432
 
        env = SimpleEnvironment('foo')
6433
 
        with temp_dir() as juju_home:
6434
 
            with env.make_jes_home(juju_home, 'bar',
6435
 
                                   {'baz': 'qux'}) as jes_home:
6436
 
                foo_path = os.path.join(jes_home, 'foo')
6437
 
                with open(foo_path, 'w') as foo:
6438
 
                    foo.write('foo')
6439
 
                self.assertTrue(os.path.isfile(foo_path))
6440
 
            with env.make_jes_home(juju_home, 'bar',
6441
 
                                   {'baz': 'qux'}) as jes_home:
6442
 
                self.assertFalse(os.path.exists(foo_path))
6443
 
 
6444
 
    def test_get_cloud_credentials_returns_config(self):
6445
 
        env = SimpleEnvironment(
6446
 
            'foo', {'type': 'ec2', 'region': 'foo'}, 'home')
6447
 
        env.credentials = {'credentials': {
6448
 
            'aws': {'credentials': {'aws': True}},
6449
 
            'azure': {'credentials': {'azure': True}},
6450
 
            }}
6451
 
        self.assertEqual(env.config, env.get_cloud_credentials())
6452
 
 
6453
 
    def test_dump_yaml(self):
6454
 
        env = SimpleEnvironment('baz', {'type': 'qux'}, 'home')
6455
 
        with temp_dir() as path:
6456
 
            env.dump_yaml(path, {'foo': 'bar'})
6457
 
            self.assertItemsEqual(
6458
 
                ['environments.yaml'], os.listdir(path))
6459
 
            with open(os.path.join(path, 'environments.yaml')) as f:
6460
 
                self.assertEqual({'foo': 'bar'}, yaml.safe_load(f))
6461
 
 
6462
 
 
6463
 
class TestJujuData(TestCase):
6464
 
 
6465
 
    def test_clone(self):
6466
 
        orig = JujuData('foo', {'type': 'bar'}, 'myhome')
6467
 
        orig.credentials = {'secret': 'password'}
6468
 
        orig.clouds = {'name': {'meta': 'data'}}
6469
 
        copy = orig.clone()
6470
 
        self.assertIs(JujuData, type(copy))
6471
 
        self.assertIsNot(orig, copy)
6472
 
        self.assertEqual(copy.environment, 'foo')
6473
 
        self.assertIsNot(orig.config, copy.config)
6474
 
        self.assertEqual({'type': 'bar'}, copy.config)
6475
 
        self.assertEqual('myhome', copy.juju_home)
6476
 
        self.assertIsNot(orig.credentials, copy.credentials)
6477
 
        self.assertEqual(orig.credentials, copy.credentials)
6478
 
        self.assertIsNot(orig.clouds, copy.clouds)
6479
 
        self.assertEqual(orig.clouds, copy.clouds)
6480
 
 
6481
 
    def test_clone_model_name(self):
6482
 
        orig = JujuData('foo', {'type': 'bar', 'name': 'oldname'}, 'myhome')
6483
 
        orig.credentials = {'secret': 'password'}
6484
 
        orig.clouds = {'name': {'meta': 'data'}}
6485
 
        copy = orig.clone(model_name='newname')
6486
 
        self.assertEqual('newname', copy.environment)
6487
 
        self.assertEqual('newname', copy.config['name'])
6488
 
 
6489
 
    def test_get_cloud_random_provider(self):
6490
 
        self.assertEqual(
6491
 
            'bar', JujuData('foo', {'type': 'bar'}, 'home').get_cloud())
6492
 
 
6493
 
    def test_get_cloud_ec2(self):
6494
 
        self.assertEqual(
6495
 
            'aws', JujuData('foo', {'type': 'ec2', 'region': 'bar'},
6496
 
                            'home').get_cloud())
6497
 
        self.assertEqual(
6498
 
            'aws-china', JujuData('foo', {
6499
 
                'type': 'ec2', 'region': 'cn-north-1'
6500
 
                }, 'home').get_cloud())
6501
 
 
6502
 
    def test_get_cloud_gce(self):
6503
 
        self.assertEqual(
6504
 
            'google', JujuData('foo', {'type': 'gce', 'region': 'bar'},
6505
 
                               'home').get_cloud())
6506
 
 
6507
 
    def test_get_cloud_maas(self):
6508
 
        data = JujuData('foo', {'type': 'maas', 'maas-server': 'bar'}, 'home')
6509
 
        data.clouds = {'clouds': {
6510
 
            'baz': {'type': 'maas', 'endpoint': 'bar'},
6511
 
            'qux': {'type': 'maas', 'endpoint': 'qux'},
6512
 
            }}
6513
 
        self.assertEqual('baz', data.get_cloud())
6514
 
 
6515
 
    def test_get_cloud_maas_wrong_type(self):
6516
 
        data = JujuData('foo', {'type': 'maas', 'maas-server': 'bar'}, 'home')
6517
 
        data.clouds = {'clouds': {
6518
 
            'baz': {'type': 'foo', 'endpoint': 'bar'},
6519
 
            }}
6520
 
        with self.assertRaisesRegexp(LookupError, 'No such endpoint: bar'):
6521
 
            self.assertEqual(data.get_cloud())
6522
 
 
6523
 
    def test_get_cloud_openstack(self):
6524
 
        data = JujuData('foo', {'type': 'openstack', 'auth-url': 'bar'},
6525
 
                        'home')
6526
 
        data.clouds = {'clouds': {
6527
 
            'baz': {'type': 'openstack', 'endpoint': 'bar'},
6528
 
            'qux': {'type': 'openstack', 'endpoint': 'qux'},
6529
 
            }}
6530
 
        self.assertEqual('baz', data.get_cloud())
6531
 
 
6532
 
    def test_get_cloud_openstack_wrong_type(self):
6533
 
        data = JujuData('foo', {'type': 'openstack', 'auth-url': 'bar'},
6534
 
                        'home')
6535
 
        data.clouds = {'clouds': {
6536
 
            'baz': {'type': 'maas', 'endpoint': 'bar'},
6537
 
            }}
6538
 
        with self.assertRaisesRegexp(LookupError, 'No such endpoint: bar'):
6539
 
            data.get_cloud()
6540
 
 
6541
 
    def test_get_region(self):
6542
 
        self.assertEqual(
6543
 
            'bar', JujuData('foo', {'type': 'foo', 'region': 'bar'},
6544
 
                            'home').get_region())
6545
 
 
6546
 
    def test_get_region_old_azure(self):
6547
 
        self.assertEqual('northeu', JujuData('foo', {
6548
 
            'type': 'azure', 'location': 'North EU'}, 'home').get_region())
6549
 
 
6550
 
    def test_get_region_azure_arm(self):
6551
 
        self.assertEqual('bar', JujuData('foo', {
6552
 
            'type': 'azure', 'location': 'bar', 'tenant-id': 'baz'},
6553
 
            'home').get_region())
6554
 
 
6555
 
    def test_get_region_joyent(self):
6556
 
        self.assertEqual('bar', JujuData('foo', {
6557
 
            'type': 'joyent', 'sdc-url': 'https://bar.api.joyentcloud.com'},
6558
 
            'home').get_region())
6559
 
 
6560
 
    def test_get_region_lxd(self):
6561
 
        self.assertEqual('localhost', JujuData('foo', {'type': 'lxd'},
6562
 
                                               'home').get_region())
6563
 
 
6564
 
    def test_get_region_maas(self):
6565
 
        self.assertIs(None, JujuData('foo', {'type': 'maas', 'region': 'bar'},
6566
 
                                     'home').get_region())
6567
 
 
6568
 
    def test_get_region_manual(self):
6569
 
        self.assertEqual('baz', JujuData('foo', {
6570
 
            'type': 'manual', 'region': 'bar',
6571
 
            'bootstrap-host': 'baz'}, 'home').get_region())
6572
 
 
6573
 
    def test_get_cloud_credentials(self):
6574
 
        juju_data = JujuData('foo', {'type': 'ec2', 'region': 'foo'}, 'home')
6575
 
        juju_data.credentials = {'credentials': {
6576
 
            'aws': {'credentials': {'aws': True}},
6577
 
            'azure': {'credentials': {'azure': True}},
6578
 
            }}
6579
 
        self.assertEqual({'aws': True}, juju_data.get_cloud_credentials())
6580
 
 
6581
 
    def test_dump_yaml(self):
6582
 
        cloud_dict = {'clouds': {'foo': {}}}
6583
 
        credential_dict = {'credential': {'bar': {}}}
6584
 
        data = JujuData('baz', {'type': 'qux'}, 'home')
6585
 
        data.clouds = dict(cloud_dict)
6586
 
        data.credentials = dict(credential_dict)
6587
 
        with temp_dir() as path:
6588
 
            data.dump_yaml(path, {})
6589
 
            self.assertItemsEqual(
6590
 
                ['clouds.yaml', 'credentials.yaml'], os.listdir(path))
6591
 
            with open(os.path.join(path, 'clouds.yaml')) as f:
6592
 
                self.assertEqual(cloud_dict, yaml.safe_load(f))
6593
 
            with open(os.path.join(path, 'credentials.yaml')) as f:
6594
 
                self.assertEqual(credential_dict, yaml.safe_load(f))
6595
 
 
6596
 
    def test_load_yaml(self):
6597
 
        cloud_dict = {'clouds': {'foo': {}}}
6598
 
        credential_dict = {'credential': {'bar': {}}}
6599
 
        with temp_dir() as path:
6600
 
            with open(os.path.join(path, 'clouds.yaml'), 'w') as f:
6601
 
                yaml.safe_dump(cloud_dict, f)
6602
 
            with open(os.path.join(path, 'credentials.yaml'), 'w') as f:
6603
 
                yaml.safe_dump(credential_dict, f)
6604
 
            data = JujuData('baz', {'type': 'qux'}, path)
6605
 
            data.load_yaml()
 
1404
            env = Environment.from_config('foo')
 
1405
            self.assertIs(Environment, type(env))
 
1406
            self.assertEqual({'type': 'local'}, env.config)
 
1407
 
 
1408
    def test_upgrade_juju_nonlocal(self):
 
1409
        client = JujuClientDevel('1.234-76', None)
 
1410
        env = Environment('foo', client, {'type': 'nonlocal'})
 
1411
        env_client = client.get_env_client(env)
 
1412
        with patch.object(client, 'get_env_client', return_value=env_client):
 
1413
            with patch.object(env_client, 'juju') as juju_mock:
 
1414
                env.upgrade_juju()
 
1415
        juju_mock.assert_called_with(
 
1416
            'upgrade-juju', ('--version', '1.234'))
 
1417
 
 
1418
    def test_get_matching_agent_version(self):
 
1419
        client = JujuClientDevel('1.23-series-arch', None)
 
1420
        env = Environment('foo', client, {'type': 'local'})
 
1421
        self.assertEqual('1.23.1', env.get_matching_agent_version())
 
1422
        self.assertEqual('1.23', env.get_matching_agent_version(
 
1423
                         no_build=True))
 
1424
        env.client.version = '1.20-beta1-series-arch'
 
1425
        self.assertEqual('1.20-beta1.1', env.get_matching_agent_version())
 
1426
 
 
1427
    def test_upgrade_juju_local(self):
 
1428
        client = JujuClientDevel(None, None)
 
1429
        env = Environment('foo', client, {'type': 'local'})
 
1430
        env.client.version = '1.234-76'
 
1431
        env_client = client.get_env_client(env)
 
1432
        with patch.object(client, 'get_env_client', return_value=env_client):
 
1433
            with patch.object(env_client, 'juju') as juju_mock:
 
1434
                env.upgrade_juju()
 
1435
        juju_mock.assert_called_with(
 
1436
            'upgrade-juju', ('--version', '1.234', '--upload-tools',))
 
1437
 
 
1438
    def test_deploy_non_joyent(self):
 
1439
        env = Environment('foo', MagicMock(), {'type': 'local'})
 
1440
        env.client.version = '1.234-76'
 
1441
        env.deploy('mondogb')
 
1442
        env.client.juju.assert_called_with(env, 'deploy', ('mondogb',))
 
1443
 
 
1444
    def test_deploy_joyent(self):
 
1445
        env = Environment('foo', MagicMock(), {'type': 'joyent'})
 
1446
        env.client.version = '1.234-76'
 
1447
        env.deploy('mondogb')
 
1448
        env.client.juju.assert_called_with(
 
1449
            env, 'deploy', ('mondogb',))
 
1450
 
 
1451
    def test_set_testing_tools_metadata_url(self):
 
1452
        client = JujuClientDevel(None, None)
 
1453
        env = Environment('foo', client)
 
1454
        with patch.object(client, 'get_env_option') as mock_get:
 
1455
            mock_get.return_value = 'https://example.org/juju/tools'
 
1456
            with patch.object(client, 'set_env_option') as mock_set:
 
1457
                env.set_testing_tools_metadata_url()
 
1458
        mock_get.assert_called_with(env, 'tools-metadata-url')
 
1459
        mock_set.assert_called_with(
 
1460
            env, 'tools-metadata-url',
 
1461
            'https://example.org/juju/testing/tools')
 
1462
 
 
1463
    def test_set_testing_tools_metadata_url_noop(self):
 
1464
        client = JujuClientDevel(None, None)
 
1465
        env = Environment('foo', client)
 
1466
        with patch.object(client, 'get_env_option') as mock_get:
 
1467
            mock_get.return_value = 'https://example.org/juju/testing/tools'
 
1468
            with patch.object(client, 'set_env_option') as mock_set:
 
1469
                env.set_testing_tools_metadata_url()
 
1470
        mock_get.assert_called_with(env, 'tools-metadata-url')
 
1471
        self.assertEqual(0, mock_set.call_count)
6606
1472
 
6607
1473
 
6608
1474
class TestGroupReporter(TestCase):
6665
1531
        self.assertEqual(sio.getvalue(), "")
6666
1532
        reporter.finish()
6667
1533
        self.assertEqual(sio.getvalue(), "")
6668
 
 
6669
 
    def test_wrap_to_width(self):
6670
 
        sio = StringIO.StringIO()
6671
 
        reporter = GroupReporter(sio, "done")
6672
 
        self.assertEqual(sio.getvalue(), "")
6673
 
        for _ in range(150):
6674
 
            reporter.update({"working": ["1"]})
6675
 
        reporter.finish()
6676
 
        self.assertEqual(sio.getvalue(), """\
6677
 
working: 1 ....................................................................
6678
 
...............................................................................
6679
 
..
6680
 
""")
6681
 
 
6682
 
    def test_wrap_to_width_exact(self):
6683
 
        sio = StringIO.StringIO()
6684
 
        reporter = GroupReporter(sio, "done")
6685
 
        reporter.wrap_width = 12
6686
 
        self.assertEqual(sio.getvalue(), "")
6687
 
        changes = []
6688
 
        for _ in range(20):
6689
 
            reporter.update({"working": ["1"]})
6690
 
            changes.append(sio.getvalue())
6691
 
        self.assertEqual(changes[::4], [
6692
 
            "working: 1",
6693
 
            "working: 1 .\n...",
6694
 
            "working: 1 .\n.......",
6695
 
            "working: 1 .\n...........",
6696
 
            "working: 1 .\n............\n...",
6697
 
        ])
6698
 
        reporter.finish()
6699
 
        self.assertEqual(sio.getvalue(), changes[-1] + "\n")
6700
 
 
6701
 
    def test_wrap_to_width_overflow(self):
6702
 
        sio = StringIO.StringIO()
6703
 
        reporter = GroupReporter(sio, "done")
6704
 
        reporter.wrap_width = 8
6705
 
        self.assertEqual(sio.getvalue(), "")
6706
 
        changes = []
6707
 
        for _ in range(16):
6708
 
            reporter.update({"working": ["1"]})
6709
 
            changes.append(sio.getvalue())
6710
 
        self.assertEqual(changes[::4], [
6711
 
            "working: 1",
6712
 
            "working: 1\n....",
6713
 
            "working: 1\n........",
6714
 
            "working: 1\n........\n....",
6715
 
        ])
6716
 
        reporter.finish()
6717
 
        self.assertEqual(sio.getvalue(), changes[-1] + "\n")
6718
 
 
6719
 
    def test_wrap_to_width_multiple_groups(self):
6720
 
        sio = StringIO.StringIO()
6721
 
        reporter = GroupReporter(sio, "done")
6722
 
        reporter.wrap_width = 16
6723
 
        self.assertEqual(sio.getvalue(), "")
6724
 
        changes = []
6725
 
        for _ in range(6):
6726
 
            reporter.update({"working": ["1", "2"]})
6727
 
            changes.append(sio.getvalue())
6728
 
        for _ in range(10):
6729
 
            reporter.update({"working": ["1"], "done": ["2"]})
6730
 
            changes.append(sio.getvalue())
6731
 
        self.assertEqual(changes[::4], [
6732
 
            "working: 1, 2",
6733
 
            "working: 1, 2 ..\n..",
6734
 
            "working: 1, 2 ..\n...\n"
6735
 
            "working: 1 ..",
6736
 
            "working: 1, 2 ..\n...\n"
6737
 
            "working: 1 .....\n.",
6738
 
        ])
6739
 
        reporter.finish()
6740
 
        self.assertEqual(sio.getvalue(), changes[-1] + "\n")
6741
 
 
6742
 
 
6743
 
class AssessParseStateServerFromErrorTestCase(TestCase):
6744
 
 
6745
 
    def test_parse_new_state_server_from_error(self):
6746
 
        output = dedent("""
6747
 
            Waiting for address
6748
 
            Attempting to connect to 10.0.0.202:22
6749
 
            Attempting to connect to 1.2.3.4:22
6750
 
            The fingerprint for the ECDSA key sent by the remote host is
6751
 
            """)
6752
 
        error = subprocess.CalledProcessError(1, ['foo'], output)
6753
 
        address = parse_new_state_server_from_error(error)
6754
 
        self.assertEqual('1.2.3.4', address)
6755
 
 
6756
 
    def test_parse_new_state_server_from_error_output_none(self):
6757
 
        error = subprocess.CalledProcessError(1, ['foo'], None)
6758
 
        address = parse_new_state_server_from_error(error)
6759
 
        self.assertIs(None, address)
6760
 
 
6761
 
    def test_parse_new_state_server_from_error_no_output(self):
6762
 
        address = parse_new_state_server_from_error(Exception())
6763
 
        self.assertIs(None, address)
6764
 
 
6765
 
 
6766
 
class TestGetMachineDNSName(TestCase):
6767
 
 
6768
 
    log_level = logging.DEBUG
6769
 
 
6770
 
    machine_0_no_addr = """\
6771
 
        machines:
6772
 
            "0":
6773
 
                instance-id: pending
6774
 
        """
6775
 
 
6776
 
    machine_0_hostname = """\
6777
 
        machines:
6778
 
            "0":
6779
 
                dns-name: a-host
6780
 
        """
6781
 
 
6782
 
    machine_0_ipv6 = """\
6783
 
        machines:
6784
 
            "0":
6785
 
                dns-name: 2001:db8::3
6786
 
        """
6787
 
 
6788
 
    def test_gets_host(self):
6789
 
        status = Status.from_text(self.machine_0_hostname)
6790
 
        fake_client = Mock(spec=['status_until'])
6791
 
        fake_client.status_until.return_value = [status]
6792
 
        host = get_machine_dns_name(fake_client, '0')
6793
 
        self.assertEqual(host, "a-host")
6794
 
        fake_client.status_until.assert_called_once_with(timeout=600)
6795
 
        self.assertEqual(self.log_stream.getvalue(), "")
6796
 
 
6797
 
    def test_retries_for_dns_name(self):
6798
 
        status_pending = Status.from_text(self.machine_0_no_addr)
6799
 
        status_host = Status.from_text(self.machine_0_hostname)
6800
 
        fake_client = Mock(spec=['status_until'])
6801
 
        fake_client.status_until.return_value = [status_pending, status_host]
6802
 
        host = get_machine_dns_name(fake_client, '0')
6803
 
        self.assertEqual(host, "a-host")
6804
 
        fake_client.status_until.assert_called_once_with(timeout=600)
6805
 
        self.assertEqual(
6806
 
            self.log_stream.getvalue(),
6807
 
            "DEBUG No dns-name yet for machine 0\n")
6808
 
 
6809
 
    def test_retries_gives_up(self):
6810
 
        status = Status.from_text(self.machine_0_no_addr)
6811
 
        fake_client = Mock(spec=['status_until'])
6812
 
        fake_client.status_until.return_value = [status] * 3
6813
 
        host = get_machine_dns_name(fake_client, '0', timeout=10)
6814
 
        self.assertEqual(host, None)
6815
 
        fake_client.status_until.assert_called_once_with(timeout=10)
6816
 
        self.assertEqual(
6817
 
            self.log_stream.getvalue(),
6818
 
            "DEBUG No dns-name yet for machine 0\n" * 3)
6819
 
 
6820
 
    def test_gets_ipv6(self):
6821
 
        status = Status.from_text(self.machine_0_ipv6)
6822
 
        fake_client = Mock(spec=['status_until'])
6823
 
        fake_client.status_until.return_value = [status]
6824
 
        host = get_machine_dns_name(fake_client, '0')
6825
 
        self.assertEqual(host, "2001:db8::3")
6826
 
        fake_client.status_until.assert_called_once_with(timeout=600)
6827
 
        self.assertEqual(
6828
 
            self.log_stream.getvalue(),
6829
 
            "WARNING Selected IPv6 address for machine 0: '2001:db8::3'\n")
6830
 
 
6831
 
    def test_gets_ipv6_unsupported(self):
6832
 
        status = Status.from_text(self.machine_0_ipv6)
6833
 
        fake_client = Mock(spec=['status_until'])
6834
 
        fake_client.status_until.return_value = [status]
6835
 
        with patch('utility.socket', wraps=socket) as wrapped_socket:
6836
 
            del wrapped_socket.inet_pton
6837
 
            host = get_machine_dns_name(fake_client, '0')
6838
 
        self.assertEqual(host, "2001:db8::3")
6839
 
        fake_client.status_until.assert_called_once_with(timeout=600)
6840
 
        self.assertEqual(self.log_stream.getvalue(), "")