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

« back to all changes in this revision

Viewing changes to tests/test_substrate.py

  • Committer: John George
  • Date: 2016-02-05 22:13:41 UTC
  • mto: This revision was merged to the branch mainline in revision 1275.
  • Revision ID: john.george@canonical.com-20160205221341-3v2px3j23ik1zw5r
virtual maas templates and setup script.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from contextlib import contextmanager
2
1
import json
3
2
import os
4
3
from subprocess import CalledProcessError
5
4
from textwrap import dedent
 
5
from unittest import TestCase
6
6
 
7
7
from boto.ec2.securitygroup import SecurityGroup
8
8
from boto.exception import EC2ResponseError
19
19
    get_euca_env,
20
20
    translate_to_env,
21
21
    )
22
 
from jujupy import (
23
 
    EnvJujuClient1X,
24
 
    JujuData,
25
 
    SimpleEnvironment,
26
 
    )
 
22
from jujupy import SimpleEnvironment
27
23
from substrate import (
28
24
    AWSAccount,
29
25
    AzureAccount,
30
 
    AzureARMAccount,
31
 
    convert_to_azure_ids,
32
26
    describe_instances,
33
27
    destroy_job_instances,
34
28
    get_job_instances,
35
29
    get_libvirt_domstate,
36
30
    JoyentAccount,
37
 
    LXDAccount,
38
31
    make_substrate_manager,
39
32
    MAASAccount,
40
 
    MAAS1Account,
41
 
    maas_account_from_config,
42
33
    OpenStackAccount,
43
34
    parse_euca,
44
35
    run_instances,
48
39
    terminate_instances,
49
40
    verify_libvirt_domain,
50
41
    )
51
 
from tests import TestCase
52
 
from tests.test_jujupy import fake_juju_client
53
 
from tests.test_winazurearm import (
54
 
    fake_init_services,
55
 
    ResourceGroup,
56
 
    ResourceGroupDetails,
57
 
    VirtualMachine,
58
 
    )
59
 
from winazurearm import ARMClient
60
42
 
61
43
 
62
44
def get_aws_env():
68
50
        })
69
51
 
70
52
 
71
 
def get_lxd_env():
72
 
    return SimpleEnvironment('mas', {
73
 
        'type': 'lxd'
74
 
        })
75
 
 
76
 
 
77
53
def get_maas_env():
78
54
    return SimpleEnvironment('mas', {
79
55
        'type': 'maas',
153
129
    def test_terminate_aws(self):
154
130
        env = get_aws_env()
155
131
        with patch('subprocess.check_call') as cc_mock:
156
 
            terminate_instances(env, ['foo', 'bar'])
 
132
            with patch('sys.stdout') as out_mock:
 
133
                terminate_instances(env, ['foo', 'bar'])
157
134
        environ = get_aws_environ(env)
158
135
        cc_mock.assert_called_with(
159
136
            ['euca-terminate-instances', 'foo', 'bar'], env=environ)
160
 
        self.assertEqual(
161
 
            self.log_stream.getvalue(),
162
 
            'INFO Deleting foo, bar.\n')
 
137
        self.assertEqual(out_mock.write.mock_calls, [
 
138
            call('Deleting foo, bar.'), call('\n')])
163
139
 
164
140
    def test_terminate_aws_none(self):
165
141
        env = get_aws_env()
166
142
        with patch('subprocess.check_call') as cc_mock:
167
 
            terminate_instances(env, [])
 
143
            with patch('sys.stdout') as out_mock:
 
144
                terminate_instances(env, [])
168
145
        self.assertEqual(cc_mock.call_count, 0)
169
 
        self.assertEqual(
170
 
            self.log_stream.getvalue(),
171
 
            'INFO No instances to delete.\n')
 
146
        self.assertEqual(out_mock.write.mock_calls, [
 
147
            call('No instances to delete.'), call('\n')])
172
148
 
173
149
    def test_terminate_maas(self):
174
150
        env = get_maas_env()
175
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
176
 
            with patch('subprocess.check_output', autospec=True,
177
 
                       return_value='{}') as co_mock:
 
151
        with patch('subprocess.check_call') as cc_mock:
 
152
            with patch('sys.stdout') as out_mock:
178
153
                terminate_instances(env, ['/A/B/C/D/node-3d/'])
179
 
        self.assertEquals(cc_mock.call_args_list, [
180
 
            call(['maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/2.0/',
181
 
                  'a:password:string']),
182
 
            call(['maas', 'logout', 'mas']),
183
 
        ])
184
 
        co_mock.assert_called_once_with(
185
 
            ('maas', 'mas', 'machine', 'release', 'node-3d'))
186
 
        self.assertEqual(
187
 
            self.log_stream.getvalue(),
188
 
            'INFO Deleting /A/B/C/D/node-3d/.\n')
 
154
        expected = (
 
155
            ['maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/1.0/',
 
156
             'a:password:string'],
 
157
        )
 
158
        self.assertEqual(expected, cc_mock.call_args_list[0][0])
 
159
        expected = (['maas', 'mas', 'node', 'release', 'node-3d'],)
 
160
        self.assertEqual(expected, cc_mock.call_args_list[1][0])
 
161
        expected = (['maas', 'logout', 'mas'],)
 
162
        self.assertEqual(expected, cc_mock.call_args_list[2][0])
 
163
        self.assertEqual(3, len(cc_mock.call_args_list))
 
164
        self.assertEqual(out_mock.write.mock_calls, [
 
165
            call('Deleting /A/B/C/D/node-3d/.'), call('\n')])
189
166
 
190
167
    def test_terminate_maas_none(self):
191
168
        env = get_maas_env()
192
169
        with patch('subprocess.check_call') as cc_mock:
193
 
            terminate_instances(env, [])
 
170
            with patch('sys.stdout') as out_mock:
 
171
                terminate_instances(env, [])
194
172
        self.assertEqual(cc_mock.call_count, 0)
195
 
        self.assertEqual(
196
 
            self.log_stream.getvalue(),
197
 
            'INFO No instances to delete.\n')
 
173
        self.assertEqual(out_mock.write.mock_calls, [
 
174
            call('No instances to delete.'), call('\n')])
198
175
 
199
176
    def test_terminate_openstack(self):
200
177
        env = get_openstack_env()
201
178
        with patch('subprocess.check_call') as cc_mock:
202
 
            terminate_instances(env, ['foo', 'bar'])
 
179
            with patch('sys.stdout') as out_mock:
 
180
                terminate_instances(env, ['foo', 'bar'])
203
181
        environ = dict(os.environ)
204
182
        environ.update(translate_to_env(env.config))
205
183
        cc_mock.assert_called_with(
206
184
            ['nova', 'delete', 'foo', 'bar'], env=environ)
207
 
        self.assertEqual(
208
 
            self.log_stream.getvalue(),
209
 
            'INFO Deleting foo, bar.\n')
 
185
        self.assertEqual(out_mock.write.mock_calls, [
 
186
            call('Deleting foo, bar.'), call('\n')])
210
187
 
211
188
    def test_terminate_openstack_none(self):
212
189
        env = get_openstack_env()
213
190
        with patch('subprocess.check_call') as cc_mock:
214
 
            terminate_instances(env, [])
 
191
            with patch('sys.stdout') as out_mock:
 
192
                terminate_instances(env, [])
215
193
        self.assertEqual(cc_mock.call_count, 0)
216
 
        self.assertEqual(
217
 
            self.log_stream.getvalue(),
218
 
            'INFO No instances to delete.\n')
 
194
        self.assertEqual(out_mock.write.mock_calls, [
 
195
            call('No instances to delete.'), call('\n')])
219
196
 
220
197
    def test_terminate_rackspace(self):
221
198
        env = get_rax_env()
222
199
        with patch('subprocess.check_call') as cc_mock:
223
 
            terminate_instances(env, ['foo', 'bar'])
 
200
            with patch('sys.stdout') as out_mock:
 
201
                terminate_instances(env, ['foo', 'bar'])
224
202
        environ = dict(os.environ)
225
203
        environ.update(translate_to_env(env.config))
226
204
        cc_mock.assert_called_with(
227
205
            ['nova', 'delete', 'foo', 'bar'], env=environ)
228
 
        self.assertEqual(
229
 
            self.log_stream.getvalue(),
230
 
            'INFO Deleting foo, bar.\n')
 
206
        self.assertEqual(out_mock.write.mock_calls, [
 
207
            call('Deleting foo, bar.'), call('\n')])
231
208
 
232
209
    def test_terminate_joyent(self):
233
210
        with patch('substrate.JoyentAccount.terminate_instances') as ti_mock:
235
212
                SimpleEnvironment('foo', get_joyent_config()), ['ab', 'cd'])
236
213
        ti_mock.assert_called_once_with(['ab', 'cd'])
237
214
 
238
 
    def test_terminate_lxd(self):
239
 
        env = get_lxd_env()
240
 
        with patch('subprocess.check_call') as cc_mock:
241
 
            terminate_instances(env, ['foo', 'bar'])
242
 
        self.assertEqual(
243
 
            [call(['lxc', 'stop', '--force', 'foo']),
244
 
             call(['lxc', 'delete', '--force', 'foo']),
245
 
             call(['lxc', 'stop', '--force', 'bar']),
246
 
             call(['lxc', 'delete', '--force', 'bar'])],
247
 
            cc_mock.mock_calls)
248
 
 
249
 
    def test_terminate_unknown(self):
 
215
    def test_terminate_uknown(self):
250
216
        env = SimpleEnvironment('foo', {'type': 'unknown'})
251
217
        with patch('subprocess.check_call') as cc_mock:
252
 
            with self.assertRaisesRegexp(
253
 
                    ValueError,
254
 
                    'This test does not support the unknown provider'):
255
 
                terminate_instances(env, ['foo'])
 
218
            with patch('sys.stdout') as out_mock:
 
219
                with self.assertRaisesRegexp(
 
220
                        ValueError,
 
221
                        'This test does not support the unknown provider'):
 
222
                    terminate_instances(env, ['foo'])
256
223
        self.assertEqual(cc_mock.call_count, 0)
257
 
        self.assertEqual(self.log_stream.getvalue(), '')
 
224
        self.assertEqual(out_mock.write.call_count, 0)
258
225
 
259
226
 
260
227
class TestAWSAccount(TestCase):
274
241
                })
275
242
            self.assertEqual(aws.region, 'france')
276
243
 
 
244
    def test_get_environ(self):
 
245
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
246
            environ = dict(os.environ)
 
247
            environ.update({
 
248
                'AWS_ACCESS_KEY': 'skeleton-key',
 
249
                'AWS_SECRET_KEY': 'secret-skeleton-key',
 
250
                'EC2_ACCESS_KEY': 'skeleton-key',
 
251
                'EC2_SECRET_KEY': 'secret-skeleton-key',
 
252
                'EC2_URL': 'https://ca-west.ec2.amazonaws.com',
 
253
                })
 
254
            self.assertEqual(aws.get_environ(), environ)
 
255
 
 
256
    def test_euca(self):
 
257
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
258
            with patch('subprocess.check_call',
 
259
                       return_value='quxx') as co_mock:
 
260
                result = aws.euca('foo-bar', ['baz', 'qux'])
 
261
            co_mock.assert_called_once_with(['euca-foo-bar', 'baz', 'qux'],
 
262
                                            env=aws.get_environ())
 
263
            self.assertEqual(result, 'quxx')
 
264
 
 
265
    def test_get_euca_output(self):
 
266
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
267
            with patch('subprocess.check_output',
 
268
                       return_value='quxx') as co_mock:
 
269
                result = aws.get_euca_output('foo-bar', ['baz', 'qux'])
 
270
            co_mock.assert_called_once_with(['euca-foo-bar', 'baz', 'qux'],
 
271
                                            env=aws.get_environ())
 
272
            self.assertEqual(result, 'quxx')
 
273
 
277
274
    def test_iter_security_groups(self):
278
 
 
279
 
        def make_group():
280
 
            class FakeGroup:
281
 
                def __init__(self, name):
282
 
                    self.name, self.id = name, name + "-id"
283
 
 
284
 
            for name in ['foo', 'foobar', 'baz']:
285
 
                group = FakeGroup(name)
286
 
                yield group
287
 
 
288
 
        client = MagicMock(spec=['get_all_security_groups'])
289
 
        client.get_all_security_groups.return_value = list(make_group())
290
 
        with patch('substrate.ec2.connect_to_region',
291
 
                   return_value=client) as ctr_mock:
292
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
275
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
276
 
 
277
            def make_group(name):
 
278
                return '\t'.join([
 
279
                    'GROUP', name + '-id', '689913858002',
 
280
                    name, 'juju group', 'vpc-1f40b47a'])
 
281
 
 
282
            return_value = ''.join(
 
283
                make_group(g) + '\n' for g in ['foo', 'foobar', 'baz'])
 
284
            return_value += 'RANDOM\n'
 
285
            return_value += '\n'
 
286
            with patch('subprocess.check_output',
 
287
                       return_value=return_value) as co_mock:
293
288
                groups = list(aws.iter_security_groups())
294
 
        self.assertEqual(groups, [
295
 
            ('foo-id', 'foo'), ('foobar-id', 'foobar'), ('baz-id', 'baz')])
296
 
        self.assert_ec2_connection_call(ctr_mock)
297
 
 
298
 
    def assert_ec2_connection_call(self, ctr_mock):
299
 
        ctr_mock.assert_called_once_with(
300
 
            'ca-west', aws_access_key_id='skeleton-key',
301
 
            aws_secret_access_key='secret-skeleton-key')
 
289
            co_mock.assert_called_once_with(
 
290
                ['euca-describe-groups', '--filter', 'description=juju group'],
 
291
                env=aws.get_environ())
 
292
            self.assertEqual(groups, [
 
293
                ('foo-id', 'foo'), ('foobar-id', 'foobar'), ('baz-id', 'baz')])
302
294
 
303
295
    def test_iter_instance_security_groups(self):
304
 
        instances = [
305
 
            MagicMock(instances=[MagicMock(groups=[
306
 
                SecurityGroup(id='foo', name='bar'), ])]),
307
 
            MagicMock(instances=[MagicMock(groups=[
308
 
                SecurityGroup(id='baz', name='qux'),
309
 
                SecurityGroup(id='quxx-id', name='quxx'), ])]),
310
 
        ]
311
 
        client = MagicMock(spec=['get_all_instances'])
312
 
        client.get_all_instances.return_value = instances
313
 
        with patch('substrate.ec2.connect_to_region',
314
 
                   return_value=client) as ctr_mock:
315
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
296
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
297
            with patch.object(aws, 'get_ec2_connection') as gec_mock:
 
298
                instances = [
 
299
                    MagicMock(instances=[MagicMock(groups=[
 
300
                        SecurityGroup(id='foo', name='bar'),
 
301
                        ])]),
 
302
                    MagicMock(instances=[MagicMock(groups=[
 
303
                        SecurityGroup(id='baz', name='qux'),
 
304
                        SecurityGroup(id='quxx-id', name='quxx'),
 
305
                        ])]),
 
306
                ]
 
307
                gai_mock = gec_mock.return_value.get_all_instances
 
308
                gai_mock.return_value = instances
316
309
                groups = list(aws.iter_instance_security_groups())
317
 
        self.assertEqual(
318
 
            groups, [('foo', 'bar'), ('baz', 'qux'), ('quxx-id', 'quxx')])
319
 
        client.get_all_instances.assert_called_once_with(instance_ids=None)
320
 
        self.assert_ec2_connection_call(ctr_mock)
 
310
            gec_mock.assert_called_once_with()
 
311
            self.assertEqual(groups, [
 
312
                ('foo', 'bar'), ('baz', 'qux'), ('quxx-id', 'quxx')])
 
313
        gai_mock.assert_called_once_with(instance_ids=None)
321
314
 
322
315
    def test_iter_instance_security_groups_instances(self):
323
 
        instances = [
324
 
            MagicMock(instances=[MagicMock(groups=[
325
 
                SecurityGroup(id='foo', name='bar'),
326
 
                ])]),
327
 
            MagicMock(instances=[MagicMock(groups=[
328
 
                SecurityGroup(id='baz', name='qux'),
329
 
                SecurityGroup(id='quxx-id', name='quxx'),
330
 
                ])]),
331
 
        ]
332
 
        client = MagicMock(spec=['get_all_instances'])
333
 
        client.get_all_instances.return_value = instances
334
 
        with patch('substrate.ec2.connect_to_region',
335
 
                   return_value=client) as ctr_mock:
336
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
337
 
                    list(aws.iter_instance_security_groups(['abc', 'def']))
338
 
        client.get_all_instances.assert_called_once_with(
339
 
            instance_ids=['abc', 'def'])
340
 
        self.assert_ec2_connection_call(ctr_mock)
 
316
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
317
            with patch.object(aws, 'get_ec2_connection') as gec_mock:
 
318
                instances = [
 
319
                    MagicMock(instances=[MagicMock(groups=[
 
320
                        SecurityGroup(id='foo', name='bar'),
 
321
                        ])]),
 
322
                    MagicMock(instances=[MagicMock(groups=[
 
323
                        SecurityGroup(id='baz', name='qux'),
 
324
                        SecurityGroup(id='quxx-id', name='quxx'),
 
325
                        ])]),
 
326
                ]
 
327
                gai_mock = gec_mock.return_value.get_all_instances
 
328
                gai_mock.return_value = instances
 
329
                list(aws.iter_instance_security_groups(['abc', 'def']))
 
330
        gai_mock.assert_called_once_with(instance_ids=['abc', 'def'])
341
331
 
342
332
    def test_destroy_security_groups(self):
343
 
        client = MagicMock(spec=['delete_security_group'])
344
 
        client.delete_security_group.return_value = True
345
 
        with patch('substrate.ec2.connect_to_region',
346
 
                   return_value=client) as ctr_mock:
347
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
333
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
334
            with patch('subprocess.check_call') as cc_mock:
348
335
                failures = aws.destroy_security_groups(
349
336
                    ['foo', 'foobar', 'baz'])
350
 
        calls = [call(name='foo'), call(name='foobar'), call(name='baz')]
351
 
        self.assertEqual(client.delete_security_group.mock_calls, calls)
352
 
        self.assertEqual(failures, [])
353
 
        self.assert_ec2_connection_call(ctr_mock)
 
337
            self.assertEqual(cc_mock.mock_calls[0], call(
 
338
                ['euca-delete-group', 'foo'], env=aws.get_environ()))
 
339
            self.assertEqual(cc_mock.mock_calls[1], call(
 
340
                ['euca-delete-group', 'foobar'], env=aws.get_environ()))
 
341
            self.assertEqual(cc_mock.mock_calls[2], call(
 
342
                ['euca-delete-group', 'baz'], env=aws.get_environ()))
 
343
            self.assertEqual(3, cc_mock.call_count)
 
344
            self.assertEqual(failures, [])
354
345
 
355
346
    def test_destroy_security_failures(self):
356
 
        client = MagicMock(spec=['delete_security_group'])
357
 
        client.delete_security_group.return_value = False
358
 
        with patch('substrate.ec2.connect_to_region',
359
 
                   return_value=client) as ctr_mock:
360
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
361
 
                failures = aws.destroy_security_groups(
362
 
                    ['foo', 'foobar', 'baz'])
363
 
        self.assertEqual(failures, ['foo', 'foobar', 'baz'])
364
 
        self.assert_ec2_connection_call(ctr_mock)
365
 
 
366
 
    @contextmanager
367
 
    def make_aws_connection(self, return_value):
368
 
        client = MagicMock(spec=['get_all_network_interfaces'])
369
 
        client.get_all_network_interfaces.return_value = return_value
370
 
        with patch('substrate.ec2.connect_to_region',
371
 
                   return_value=client) as ctr_mock:
372
 
            with AWSAccount.manager_from_config(get_aws_env().config) as aws:
373
 
                yield aws
374
 
        self.assert_ec2_connection_call(ctr_mock)
 
347
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
348
            with patch('subprocess.check_call',
 
349
                       side_effect=CalledProcessError(1, 'foo')):
 
350
                failures = aws.destroy_security_groups(['foo', 'foobar',
 
351
                                                        'baz'])
 
352
            self.assertEqual(failures, ['foo', 'foobar', 'baz'])
 
353
 
 
354
    def test_get_ec2_connection(self):
 
355
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
356
            return_value = object()
 
357
            with patch('boto.ec2.connect_to_region',
 
358
                       return_value=return_value) as ctr_mock:
 
359
                connection = aws.get_ec2_connection()
 
360
            ctr_mock.assert_called_once_with(
 
361
                'ca-west', aws_access_key_id='skeleton-key',
 
362
                aws_secret_access_key='secret-skeleton-key')
 
363
            self.assertIs(connection, return_value)
 
364
 
 
365
    def make_aws_connection(self):
 
366
        with AWSAccount.manager_from_config(get_aws_env().config) as aws:
 
367
            aws.get_ec2_connection = MagicMock()
 
368
            connection = aws.get_ec2_connection.return_value
 
369
            return aws, connection
375
370
 
376
371
    def make_interface(self, group_ids):
377
 
        interface = MagicMock(spec=['groups', 'delete', 'id'])
 
372
        interface = MagicMock()
378
373
        interface.groups = [SecurityGroup(id=g) for g in group_ids]
379
374
        return interface
380
375
 
381
376
    def test_delete_detached_interfaces_with_id(self):
 
377
        aws, connection = self.make_aws_connection()
382
378
        foo_interface = self.make_interface(['bar-id'])
383
379
        baz_interface = self.make_interface(['baz-id', 'bar-id'])
384
 
        with self.make_aws_connection([foo_interface, baz_interface]) as aws:
385
 
            unclean = aws.delete_detached_interfaces(['bar-id'])
386
 
            foo_interface.delete.assert_called_once_with()
387
 
            baz_interface.delete.assert_called_once_with()
 
380
        gani_mock = connection.get_all_network_interfaces
 
381
        gani_mock.return_value = [foo_interface, baz_interface]
 
382
        unclean = aws.delete_detached_interfaces(['bar-id'])
 
383
        gani_mock.assert_called_once_with(
 
384
            filters={'status': 'available'})
 
385
        foo_interface.delete.assert_called_once_with()
 
386
        baz_interface.delete.assert_called_once_with()
388
387
        self.assertEqual(unclean, set())
389
388
 
390
389
    def test_delete_detached_interfaces_without_id(self):
 
390
        aws, connection = self.make_aws_connection()
391
391
        baz_interface = self.make_interface(['baz-id'])
392
 
        with self.make_aws_connection([baz_interface]) as aws:
393
 
            unclean = aws.delete_detached_interfaces(['bar-id'])
 
392
        connection.get_all_network_interfaces.return_value = [baz_interface]
 
393
        unclean = aws.delete_detached_interfaces(['bar-id'])
394
394
        self.assertEqual(baz_interface.delete.call_count, 0)
395
395
        self.assertEqual(unclean, set())
396
396
 
397
397
    def prepare_delete_exception(self, error_code):
 
398
        aws, connection = self.make_aws_connection()
398
399
        baz_interface = self.make_interface(['bar-id'])
399
400
        e = EC2ResponseError('status', 'reason')
400
401
        e.error_code = error_code
401
402
        baz_interface.delete.side_effect = e
402
 
        return baz_interface
 
403
        connection.get_all_network_interfaces.return_value = [baz_interface]
 
404
        return aws, baz_interface
403
405
 
404
406
    def test_delete_detached_interfaces_in_use(self):
405
 
        baz_interface = self.prepare_delete_exception(
 
407
        aws, baz_interface = self.prepare_delete_exception(
406
408
            'InvalidNetworkInterface.InUse')
407
 
        with self.make_aws_connection([baz_interface]) as aws:
408
 
            unclean = aws.delete_detached_interfaces(['bar-id', 'foo-id'])
 
409
        unclean = aws.delete_detached_interfaces(['bar-id', 'foo-id'])
409
410
        baz_interface.delete.assert_called_once_with()
410
411
        self.assertEqual(unclean, set(['bar-id']))
411
412
 
412
413
    def test_delete_detached_interfaces_not_found(self):
413
 
        baz_interface = self.prepare_delete_exception(
 
414
        aws, baz_interface = self.prepare_delete_exception(
414
415
            'InvalidNetworkInterfaceID.NotFound')
415
 
        with self.make_aws_connection([baz_interface]) as aws:
416
 
            unclean = aws.delete_detached_interfaces(['bar-id', 'foo-id'])
 
416
        unclean = aws.delete_detached_interfaces(['bar-id', 'foo-id'])
417
417
        baz_interface.delete.assert_called_once_with()
418
418
        self.assertEqual(unclean, set(['bar-id']))
419
419
 
420
420
    def test_delete_detached_interfaces_other(self):
421
 
        baz_interface = self.prepare_delete_exception(
 
421
        aws, baz_interface = self.prepare_delete_exception(
422
422
            'InvalidNetworkInterfaceID')
423
 
        with self.make_aws_connection([baz_interface]) as aws:
424
 
            with self.assertRaises(EC2ResponseError):
425
 
                aws.delete_detached_interfaces(['bar-id', 'foo-id'])
 
423
        with self.assertRaises(EC2ResponseError):
 
424
            aws.delete_detached_interfaces(['bar-id', 'foo-id'])
426
425
 
427
426
 
428
427
def get_os_config():
582
581
        client.delete_machine.assert_called_once_with('a')
583
582
 
584
583
 
585
 
def get_lxd_config():
586
 
    return {'type': 'lxd'}
587
 
 
588
 
 
589
 
class TestLXDAccount(TestCase):
590
 
 
591
 
    def test_manager_from_config(self):
592
 
        config = get_lxd_config()
593
 
        with LXDAccount.manager_from_config(config) as account:
594
 
            self.assertIsNone(account.remote)
595
 
        config['region'] = 'lxd-server'
596
 
        with LXDAccount.manager_from_config(config) as account:
597
 
            self.assertEqual('lxd-server', account.remote)
598
 
 
599
 
    def test_terminate_instances(self):
600
 
        account = LXDAccount()
601
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
602
 
            account.terminate_instances(['asdf'])
603
 
        self.assertEqual(
604
 
            [call(['lxc', 'stop', '--force', 'asdf']),
605
 
             call(['lxc', 'delete', '--force', 'asdf'])],
606
 
            cc_mock.mock_calls)
607
 
 
608
 
    def test_terminate_instances_with_remote(self):
609
 
        account = LXDAccount(remote='lxd-server')
610
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
611
 
            account.terminate_instances(['asdf'])
612
 
        self.assertEqual(
613
 
            [call(['lxc', 'stop', '--force', 'asdf']),
614
 
             call(['lxc', 'delete', '--force', 'lxd-server:asdf'])],
615
 
            cc_mock.mock_calls)
616
 
 
617
 
 
618
584
def make_sms(instance_ids):
619
585
    from azure import servicemanagement as sm
620
586
    client = create_autospec(sm.ServiceManagementService('foo', 'bar'))
689
655
        client.delete_hosted_service.assert_called_once_with('foo')
690
656
 
691
657
 
692
 
def get_azure_config():
693
 
    return {
694
 
        'type': 'azure',
695
 
        'subscription-id': 'subscription-id',
696
 
        'application-id': 'application-id',
697
 
        'application-password': 'application-password',
698
 
        'tenant-id': 'tenant-id'
699
 
    }
700
 
 
701
 
 
702
 
class TestAzureARMAccount(TestCase):
703
 
 
704
 
    @patch('winazurearm.ARMClient.init_services',
705
 
           autospec=True, side_effect=fake_init_services)
706
 
    def test_manager_from_config(self, is_mock):
707
 
        config = get_azure_config()
708
 
        with AzureARMAccount.manager_from_config(config) as substrate:
709
 
            self.assertEqual(
710
 
                substrate.arm_client.subscription_id, 'subscription-id')
711
 
            self.assertEqual(substrate.arm_client.client_id, 'application-id')
712
 
            self.assertEqual(
713
 
                substrate.arm_client.secret, 'application-password')
714
 
            self.assertEqual(substrate.arm_client.tenant, 'tenant-id')
715
 
            is_mock.assert_called_once_with(substrate.arm_client)
716
 
 
717
 
    @patch('winazurearm.ARMClient.init_services',
718
 
           autospec=True, side_effect=fake_init_services)
719
 
    def test_terminate_instances(self, is_mock):
720
 
        config = get_azure_config()
721
 
        arm_client = ARMClient(
722
 
            config['subscription-id'], config['application-id'],
723
 
            config['application-password'], config['tenant-id'])
724
 
        account = AzureARMAccount(arm_client)
725
 
        with patch('winazurearm.delete_instance', autospec=True) as di_mock:
726
 
            account.terminate_instances(['foo-bar'])
727
 
        di_mock.assert_called_once_with(
728
 
            arm_client, 'foo-bar', resource_group=None)
729
 
 
730
 
    @patch('winazurearm.ARMClient.init_services',
731
 
           autospec=True, side_effect=fake_init_services)
732
 
    def test_convert_to_azure_ids(self, is_mock):
733
 
        env = JujuData('controller', get_azure_config(), juju_home='data')
734
 
        client = fake_juju_client(env=env)
735
 
 
736
 
        arm_client = ARMClient(
737
 
            'subscription-id', 'application-id', 'application-password',
738
 
            'tenant-id')
739
 
        account = AzureARMAccount(arm_client)
740
 
        group = ResourceGroup(name='juju-controller-model-bar')
741
 
        virtual_machine = VirtualMachine('machine-0', 'abcd-1234')
742
 
        other_machine = VirtualMachine('machine-1', 'bcde-1234')
743
 
        fake_listed = [ResourceGroupDetails(
744
 
            arm_client, group, vms=[virtual_machine, other_machine])]
745
 
        models = {'models': [
746
 
            {'name': 'controller',
747
 
                'model-uuid': 'bar', 'controller-uuid': 'bar'},
748
 
            {'name': 'default',
749
 
                'model-uuid': 'baz', 'controller-uuid': 'bar'},
750
 
            ]}
751
 
        with patch.object(client, 'get_models', autospec=True,
752
 
                          return_value=models) as gm_mock:
753
 
            with patch('winazurearm.list_resources', autospec=True,
754
 
                       return_value=fake_listed) as lr_mock:
755
 
                ids = account.convert_to_azure_ids(client, ['machine-0'])
756
 
        self.assertEqual(['abcd-1234'], ids)
757
 
        gm_mock.assert_called_once_with()
758
 
        lr_mock.assert_called_once_with(
759
 
            arm_client, glob='juju-controller-model-bar', recursive=True)
760
 
 
761
 
    @patch('winazurearm.ARMClient.init_services',
762
 
           autospec=True, side_effect=fake_init_services)
763
 
    def test_convert_to_azure_ids_function(self, is_mock):
764
 
        env = JujuData('controller', get_azure_config(), juju_home='data')
765
 
        client = fake_juju_client(env=env)
766
 
        arm_client = ARMClient(
767
 
            'subscription-id', 'application-id', 'application-password',
768
 
            'tenant-id')
769
 
        group = ResourceGroup(name='juju-controller-model-bar')
770
 
        virtual_machine = VirtualMachine('machine-0', 'abcd-1234')
771
 
        other_machine = VirtualMachine('machine-1', 'bcde-1234')
772
 
        fake_listed = [ResourceGroupDetails(
773
 
            arm_client, group, vms=[virtual_machine, other_machine])]
774
 
        models = {'models': [
775
 
            {'name': 'controller',
776
 
                'model-uuid': 'bar', 'controller-uuid': 'bar'},
777
 
            {'name': 'default',
778
 
                'model-uuid': 'baz', 'controller-uuid': 'bar'},
779
 
            ]}
780
 
        with patch.object(client, 'get_models', autospec=True,
781
 
                          return_value=models) as gm_mock:
782
 
            with patch('winazurearm.list_resources', autospec=True,
783
 
                       return_value=fake_listed) as lr_mock:
784
 
                ids = convert_to_azure_ids(client, ['machine-0'])
785
 
        self.assertEqual(['abcd-1234'], ids)
786
 
        gm_mock.assert_called_once_with()
787
 
        lr_mock.assert_called_once_with(
788
 
            arm_client, glob='juju-controller-model-bar', recursive=True)
789
 
 
790
 
    def test_convert_to_azure_ids_function_1x_client(self):
791
 
        env = SimpleEnvironment('foo', config=get_azure_config())
792
 
        client = fake_juju_client(env=env, version='1.2', cls=EnvJujuClient1X)
793
 
        with patch.object(client, 'get_models') as gm_mock:
794
 
            with patch('winazurearm.list_resources') as lr_mock:
795
 
                ids = convert_to_azure_ids(client, ['a-sensible-id'])
796
 
        self.assertEqual(['a-sensible-id'], ids)
797
 
        self.assertEqual(0, gm_mock.call_count)
798
 
        self.assertEqual(0, lr_mock.call_count)
799
 
 
800
 
    @patch('winazurearm.ARMClient.init_services',
801
 
           autospec=True, side_effect=fake_init_services)
802
 
    def test_convert_to_azure_ids_function_bug_1586089_fixed(self, is_mock):
803
 
        env = JujuData('controller', get_azure_config(), juju_home='data')
804
 
        client = fake_juju_client(env=env, version='2.1')
805
 
        with patch.object(client, 'get_models') as gm_mock:
806
 
            with patch('winazurearm.list_resources') as lr_mock:
807
 
                ids = convert_to_azure_ids(client, ['a-sensible-id'])
808
 
        self.assertEqual(['a-sensible-id'], ids)
809
 
        self.assertEqual(0, gm_mock.call_count)
810
 
        self.assertEqual(0, lr_mock.call_count)
811
 
 
812
 
 
813
 
class TestMAASAccount(TestCase):
814
 
 
815
 
    def get_account(self):
816
 
        """Give a MAASAccount for testing."""
817
 
        config = get_maas_env().config
818
 
        return MAASAccount(
819
 
            config['name'], config['maas-server'], config['maas-oauth'])
820
 
 
821
 
    @patch('subprocess.check_call', autospec=True)
822
 
    def test_login(self, cc_mock):
823
 
        account = self.get_account()
824
 
        account.login()
825
 
        cc_mock.assert_called_once_with([
826
 
            'maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/2.0/',
827
 
            'a:password:string'])
828
 
 
829
 
    @patch('subprocess.check_call', autospec=True)
830
 
    def test_logout(self, cc_mock):
831
 
        account = self.get_account()
832
 
        account.logout()
833
 
        cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
834
 
 
835
 
    def test_terminate_instances(self):
836
 
        account = self.get_account()
837
 
        instance_ids = ['/A/B/C/D/node-1d/', '/A/B/C/D/node-2d/']
838
 
        with patch('subprocess.check_output', autospec=True,
839
 
                   return_value='{}') as co_mock:
840
 
            account.terminate_instances(instance_ids)
841
 
        co_mock.assert_any_call(
842
 
            ('maas', 'mas', 'machine', 'release', 'node-1d'))
843
 
        co_mock.assert_called_with(
844
 
            ('maas', 'mas', 'machine', 'release', 'node-2d'))
845
 
 
846
 
    def test_get_allocated_nodes(self):
847
 
        account = self.get_account()
848
 
        node = make_maas_node('maas-node-1.maas')
849
 
        allocated_nodes_string = '[%s]' % json.dumps(node)
850
 
        with patch('subprocess.check_output', autospec=True,
851
 
                   return_value=allocated_nodes_string) as co_mock:
852
 
            allocated = account.get_allocated_nodes()
853
 
        co_mock.assert_called_once_with(
854
 
            ('maas', 'mas', 'machines', 'list-allocated'))
855
 
        self.assertEqual(node, allocated['maas-node-1.maas'])
856
 
 
857
 
    def test_get_allocated_ips(self):
858
 
        account = self.get_account()
859
 
        node = make_maas_node('maas-node-1.maas')
860
 
        allocated_nodes_string = '[%s]' % json.dumps(node)
861
 
        with patch('subprocess.check_output', autospec=True,
862
 
                   return_value=allocated_nodes_string) as co_mock:
863
 
            ips = account.get_allocated_ips()
864
 
        co_mock.assert_called_once_with(
865
 
            ('maas', 'mas', 'machines', 'list-allocated'))
866
 
        self.assertEqual('10.0.30.165', ips['maas-node-1.maas'])
867
 
 
868
 
    def test_get_allocated_ips_empty(self):
869
 
        account = self.get_account()
870
 
        node = make_maas_node('maas-node-1.maas')
871
 
        node['ip_addresses'] = []
872
 
        allocated_nodes_string = '[%s]' % json.dumps(node)
873
 
        with patch('subprocess.check_output', autospec=True,
874
 
                   return_value=allocated_nodes_string) as co_mock:
875
 
            ips = account.get_allocated_ips()
876
 
        co_mock.assert_called_once_with(
877
 
            ('maas', 'mas', 'machines', 'list-allocated'))
878
 
        self.assertEqual({}, ips)
879
 
 
880
 
    def test_fabrics(self):
881
 
        account = self.get_account()
882
 
        with patch('subprocess.check_output', autospec=True,
883
 
                   return_value='[]') as co_mock:
884
 
            fabrics = account.fabrics()
885
 
        co_mock.assert_called_once_with(('maas', 'mas', 'fabrics', 'read'))
886
 
        self.assertEqual([], fabrics)
887
 
 
888
 
    def test_create_fabric(self):
889
 
        account = self.get_account()
890
 
        with patch('subprocess.check_output', autospec=True,
891
 
                   return_value='{"id": 1}') as co_mock:
892
 
            fabric = account.create_fabric('a-fabric')
893
 
            co_mock.assert_called_once_with((
894
 
                'maas', 'mas', 'fabrics', 'create', 'name=a-fabric'))
895
 
            self.assertEqual({'id': 1}, fabric)
896
 
            co_mock.reset_mock()
897
 
            fabric = account.create_fabric('a-fabric', class_type='something')
898
 
            co_mock.assert_called_once_with((
899
 
                'maas', 'mas', 'fabrics', 'create', 'name=a-fabric',
900
 
                'class_type=something'))
901
 
            self.assertEqual({'id': 1}, fabric)
902
 
 
903
 
    def test_delete_fabric(self):
904
 
        account = self.get_account()
905
 
        with patch('subprocess.check_output', autospec=True,
906
 
                   return_value='') as co_mock:
907
 
            result = account.delete_fabric(1)
908
 
        co_mock.assert_called_once_with(
909
 
            ('maas', 'mas', 'fabric', 'delete', '1'))
910
 
        self.assertEqual(None, result)
911
 
 
912
 
    def test_spaces(self):
913
 
        account = self.get_account()
914
 
        with patch('subprocess.check_output', autospec=True,
915
 
                   return_value='[]') as co_mock:
916
 
            spaces = account.spaces()
917
 
        co_mock.assert_called_once_with(('maas', 'mas', 'spaces', 'read'))
918
 
        self.assertEqual([], spaces)
919
 
 
920
 
    def test_create_space(self):
921
 
        account = self.get_account()
922
 
        with patch('subprocess.check_output', autospec=True,
923
 
                   return_value='{"id": 1}') as co_mock:
924
 
            fabric = account.create_space('a-space')
925
 
        co_mock.assert_called_once_with((
926
 
            'maas', 'mas', 'spaces', 'create', 'name=a-space'))
927
 
        self.assertEqual({'id': 1}, fabric)
928
 
 
929
 
    def test_delete_space(self):
930
 
        account = self.get_account()
931
 
        with patch('subprocess.check_output', autospec=True,
932
 
                   return_value='') as co_mock:
933
 
            result = account.delete_space(1)
934
 
        co_mock.assert_called_once_with(
935
 
            ('maas', 'mas', 'space', 'delete', '1'))
936
 
        self.assertEqual(None, result)
937
 
 
938
 
    def test_create_vlan(self):
939
 
        account = self.get_account()
940
 
        with patch('subprocess.check_output', autospec=True,
941
 
                   return_value='{"id": 5000}') as co_mock:
942
 
            vlan = account.create_vlan(0, 1)
943
 
            co_mock.assert_called_once_with((
944
 
                'maas', 'mas', 'vlans', 'create', '0', 'vid=1'))
945
 
            self.assertEqual({'id': 5000}, vlan)
946
 
            co_mock.reset_mock()
947
 
            vlan = account.create_vlan(1, 2, name='a-vlan')
948
 
            co_mock.assert_called_once_with((
949
 
                'maas', 'mas', 'vlans', 'create', '1', 'vid=2', 'name=a-vlan'))
950
 
            self.assertEqual({'id': 5000}, vlan)
951
 
 
952
 
    def test_delete_vlan(self):
953
 
        account = self.get_account()
954
 
        with patch('subprocess.check_output', autospec=True,
955
 
                   return_value='') as co_mock:
956
 
            result = account.delete_vlan(0, 4096)
957
 
        co_mock.assert_called_once_with(
958
 
            ('maas', 'mas', 'vlan', 'delete', '0', '4096'))
959
 
        self.assertEqual(None, result)
960
 
 
961
 
    def test_interfaces(self):
962
 
        account = self.get_account()
963
 
        with patch('subprocess.check_output', autospec=True,
964
 
                   return_value='[]') as co_mock:
965
 
            interfaces = account.interfaces('node-xyz')
966
 
        co_mock.assert_called_once_with((
967
 
            'maas', 'mas', 'interfaces', 'read', 'node-xyz'))
968
 
        self.assertEqual([], interfaces)
969
 
 
970
 
    def test_interface_create_vlan(self):
971
 
        account = self.get_account()
972
 
        with patch('subprocess.check_output', autospec=True,
973
 
                   return_value='{"id": 10}') as co_mock:
974
 
            interface = account.interface_create_vlan('node-xyz', 1, 5000)
975
 
        co_mock.assert_called_once_with((
976
 
            'maas', 'mas', 'interfaces', 'create-vlan', 'node-xyz', 'parent=1',
977
 
            'vlan=5000'))
978
 
        self.assertEqual({'id': 10}, interface)
979
 
 
980
 
    def test_delete_interface(self):
981
 
        account = self.get_account()
982
 
        with patch('subprocess.check_output', autospec=True,
983
 
                   return_value='') as co_mock:
984
 
            result = account.delete_interface('node-xyz', 10)
985
 
        co_mock.assert_called_once_with(
986
 
            ('maas', 'mas', 'interface', 'delete', 'node-xyz', '10'))
987
 
        self.assertEqual(None, result)
988
 
 
989
 
    def test_interface_link_subnet(self):
990
 
        account = self.get_account()
991
 
        with patch('subprocess.check_output', autospec=True,
992
 
                   return_value='{"id": 10}') as co_mock:
993
 
            subnet = account.interface_link_subnet('node-xyz', 10, 'AUTO', 40)
994
 
            co_mock.assert_called_once_with((
995
 
                'maas', 'mas', 'interface', 'link-subnet', 'node-xyz', '10',
996
 
                'mode=AUTO', 'subnet=40'))
997
 
            self.assertEqual({'id': 10}, subnet)
998
 
            co_mock.reset_mock()
999
 
            subnet = account.interface_link_subnet(
1000
 
                'node-xyz', 10, 'STATIC', 40, ip_address='10.0.10.2',
1001
 
                default_gateway=True)
1002
 
            co_mock.assert_called_once_with((
1003
 
                'maas', 'mas', 'interface', 'link-subnet', 'node-xyz', '10',
1004
 
                'mode=STATIC', 'subnet=40', 'ip_address=10.0.10.2',
1005
 
                'default_gateway=true'))
1006
 
            self.assertEqual({'id': 10}, subnet)
1007
 
 
1008
 
    def test_interface_link_subnet_invalid(self):
1009
 
        account = self.get_account()
1010
 
        with patch('subprocess.check_output', autospec=True,
1011
 
                   return_value='') as co_mock:
1012
 
            err_pattern = '^Invalid subnet connection mode: MAGIC$'
1013
 
            with self.assertRaisesRegexp(ValueError, err_pattern):
1014
 
                account.interface_link_subnet('node-xyz', 10, 'MAGIC', 40)
1015
 
            err_pattern = '^Must be mode STATIC for ip_address$'
1016
 
            with self.assertRaisesRegexp(ValueError, err_pattern):
1017
 
                account.interface_link_subnet(
1018
 
                    'node-xyz', 10, 'AUTO', 40, ip_address='127.0.0.1')
1019
 
            err_pattern = '^Must be mode AUTO or STATIC for default_gateway$'
1020
 
            with self.assertRaisesRegexp(ValueError, err_pattern):
1021
 
                account.interface_link_subnet(
1022
 
                    'node-xyz', 10, 'LINK_UP', 40, default_gateway=True)
1023
 
        self.assertEqual(0, co_mock.call_count)
1024
 
 
1025
 
    def test_interface_unlink_subnet(self):
1026
 
        account = self.get_account()
1027
 
        with patch('subprocess.check_output', autospec=True,
1028
 
                   return_value='') as co_mock:
1029
 
            result = account.interface_unlink_subnet('node-xyz', 10, 20000)
1030
 
        co_mock.assert_called_once_with((
1031
 
            'maas', 'mas', 'interface', 'unlink-subnet', 'node-xyz', '10',
1032
 
            'id=20000'))
1033
 
        self.assertEqual(None, result)
1034
 
 
1035
 
    def test_subnets(self):
1036
 
        account = self.get_account()
1037
 
        with patch('subprocess.check_output', autospec=True,
1038
 
                   return_value='[]') as co_mock:
1039
 
            subnets = account.subnets()
1040
 
        co_mock.assert_called_once_with(('maas', 'mas', 'subnets', 'read'))
1041
 
        self.assertEqual([], subnets)
1042
 
 
1043
 
    def test_create_subnet(self):
1044
 
        account = self.get_account()
1045
 
        with patch('subprocess.check_output', autospec=True,
1046
 
                   return_value='{"id": 1}') as co_mock:
1047
 
            subnet = account.create_subnet('10.0.0.0/24')
1048
 
            co_mock.assert_called_once_with((
1049
 
                'maas', 'mas', 'subnets', 'create', 'cidr=10.0.0.0/24'))
1050
 
            self.assertEqual({'id': 1}, subnet)
1051
 
            co_mock.reset_mock()
1052
 
            subnet = account.create_subnet(
1053
 
                '10.10.0.0/24', name='test-subnet', fabric_id='1', vlan_id='5',
1054
 
                space='2', gateway_ip='10.10.0.1')
1055
 
            co_mock.assert_called_once_with((
1056
 
                'maas', 'mas', 'subnets', 'create', 'cidr=10.10.0.0/24',
1057
 
                'name=test-subnet', 'fabric=1', 'vlan=5', 'space=2',
1058
 
                'gateway_ip=10.10.0.1'))
1059
 
            self.assertEqual({'id': 1}, subnet)
1060
 
            co_mock.reset_mock()
1061
 
            subnet = account.create_subnet(
1062
 
                '10.10.0.0/24', name='test-subnet', fabric_id='1', vid='0',
1063
 
                space='2', dns_servers='8.8.8.8,8.8.4.4')
1064
 
            co_mock.assert_called_once_with((
1065
 
                'maas', 'mas', 'subnets', 'create', 'cidr=10.10.0.0/24',
1066
 
                'name=test-subnet', 'fabric=1', 'vid=0', 'space=2',
1067
 
                'dns_servers=8.8.8.8,8.8.4.4'))
1068
 
            self.assertEqual({'id': 1}, subnet)
1069
 
 
1070
 
    def test_create_subnet_invalid(self):
1071
 
        account = self.get_account()
1072
 
        with patch('subprocess.check_output', autospec=True,
1073
 
                   return_value='') as co_mock:
1074
 
            err_pattern = '^Must only give one of vlan_id and vid$'
1075
 
            with self.assertRaisesRegexp(ValueError, err_pattern):
1076
 
                account.create_subnet('10.0.0.0/24', vlan_id=10, vid=1)
1077
 
        self.assertEqual(0, co_mock.call_count)
1078
 
 
1079
 
    def test_delete_subnet(self):
1080
 
        account = self.get_account()
1081
 
        with patch('subprocess.check_output', autospec=True,
1082
 
                   return_value='') as co_mock:
1083
 
            result = account.delete_subnet(1)
1084
 
        co_mock.assert_called_once_with(
1085
 
            ('maas', 'mas', 'subnet', 'delete', '1'))
1086
 
        self.assertEqual(None, result)
1087
 
 
1088
 
 
1089
 
class TestMAAS1Account(TestCase):
1090
 
 
1091
 
    def get_account(self):
1092
 
        """Give a MAAS1Account for testing."""
1093
 
        config = get_maas_env().config
1094
 
        return MAAS1Account(
1095
 
            config['name'], config['maas-server'], config['maas-oauth'])
1096
 
 
1097
 
    @patch('subprocess.check_call', autospec=True)
1098
 
    def test_login(self, cc_mock):
1099
 
        account = self.get_account()
 
658
class TestMAASAcount(TestCase):
 
659
 
 
660
    @patch.object(MAASAccount, 'logout', autospec=True)
 
661
    @patch.object(MAASAccount, 'login', autospec=True)
 
662
    def test_manager_from_config(self, li_mock, lo_mock):
 
663
        config = get_maas_env().config
 
664
        with MAASAccount.manager_from_config(config) as account:
 
665
            self.assertEqual(account.profile, 'mas')
 
666
            self.assertEqual(account.url, 'http://10.0.10.10/MAAS/api/1.0/')
 
667
            self.assertEqual(account.oauth, 'a:password:string')
 
668
            # As the class object is patched, the mocked methods
 
669
            # show that self is passed.
 
670
            li_mock.assert_called_once_with(account)
 
671
        lo_mock.assert_called_once_with(account)
 
672
 
 
673
    @patch('subprocess.check_call', autospec=True)
 
674
    def test_login(self, cc_mock):
 
675
        config = get_maas_env().config
 
676
        account = MAASAccount(
 
677
            config['name'], config['maas-server'], config['maas-oauth'])
1100
678
        account.login()
1101
679
        cc_mock.assert_called_once_with([
1102
680
            'maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/1.0/',
1104
682
 
1105
683
    @patch('subprocess.check_call', autospec=True)
1106
684
    def test_logout(self, cc_mock):
1107
 
        account = self.get_account()
 
685
        config = get_maas_env().config
 
686
        account = MAASAccount(
 
687
            config['name'], config['maas-server'], config['maas-oauth'])
1108
688
        account.logout()
1109
689
        cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
1110
690
 
1111
 
    def test_terminate_instances(self):
1112
 
        account = self.get_account()
 
691
    @patch('subprocess.check_call', autospec=True)
 
692
    def test_terminate_instances(self, cc_mock):
 
693
        config = get_maas_env().config
 
694
        account = MAASAccount(
 
695
            config['name'], config['maas-server'], config['maas-oauth'])
1113
696
        instance_ids = ['/A/B/C/D/node-1d/', '/A/B/C/D/node-2d/']
1114
 
        with patch('subprocess.check_output', autospec=True,
1115
 
                   return_value='{}') as co_mock:
1116
 
            account.terminate_instances(instance_ids)
1117
 
        co_mock.assert_any_call(
1118
 
            ('maas', 'mas', 'node', 'release', 'node-1d'))
1119
 
        co_mock.assert_called_with(
1120
 
            ('maas', 'mas', 'node', 'release', 'node-2d'))
 
697
        account.terminate_instances(instance_ids)
 
698
        cc_mock.assert_any_call(
 
699
            ['maas', 'mas', 'node', 'release', 'node-1d'])
 
700
        cc_mock.assert_called_with(
 
701
            ['maas', 'mas', 'node', 'release', 'node-2d'])
1121
702
 
1122
 
    def test_get_allocated_nodes(self):
1123
 
        account = self.get_account()
 
703
    @patch('subprocess.check_call', autospec=True)
 
704
    def test_get_allocated_nodes(self, cc_mock):
 
705
        config = get_maas_env().config
 
706
        account = MAASAccount(
 
707
            config['name'], config['maas-server'], config['maas-oauth'])
1124
708
        node = make_maas_node('maas-node-1.maas')
1125
709
        allocated_nodes_string = '[%s]' % json.dumps(node)
1126
710
        with patch('subprocess.check_output', autospec=True,
1127
711
                   return_value=allocated_nodes_string) as co_mock:
1128
712
            allocated = account.get_allocated_nodes()
1129
713
        co_mock.assert_called_once_with(
1130
 
            ('maas', 'mas', 'nodes', 'list-allocated'))
 
714
            ['maas', 'mas', 'nodes', 'list-allocated'])
1131
715
        self.assertEqual(node, allocated['maas-node-1.maas'])
1132
716
 
1133
 
    def test_get_allocated_ips(self):
1134
 
        account = self.get_account()
 
717
    @patch('subprocess.check_call', autospec=True)
 
718
    def test_get_allocated_ips(self, cc_mock):
 
719
        config = get_maas_env().config
 
720
        account = MAASAccount(
 
721
            config['name'], config['maas-server'], config['maas-oauth'])
1135
722
        node = make_maas_node('maas-node-1.maas')
1136
723
        allocated_nodes_string = '[%s]' % json.dumps(node)
1137
724
        with patch('subprocess.check_output', autospec=True,
1138
 
                   return_value=allocated_nodes_string) as co_mock:
 
725
                   return_value=allocated_nodes_string):
1139
726
            ips = account.get_allocated_ips()
1140
 
        co_mock.assert_called_once_with(
1141
 
            ('maas', 'mas', 'nodes', 'list-allocated'))
1142
727
        self.assertEqual('10.0.30.165', ips['maas-node-1.maas'])
1143
728
 
1144
 
    def test_get_allocated_ips_empty(self):
1145
 
        account = self.get_account()
 
729
    @patch('subprocess.check_call', autospec=True)
 
730
    def test_get_allocated_ips_empty(self, cc_mock):
 
731
        config = get_maas_env().config
 
732
        account = MAASAccount(
 
733
            config['name'], config['maas-server'], config['maas-oauth'])
1146
734
        node = make_maas_node('maas-node-1.maas')
1147
735
        node['ip_addresses'] = []
1148
736
        allocated_nodes_string = '[%s]' % json.dumps(node)
1149
737
        with patch('subprocess.check_output', autospec=True,
1150
 
                   return_value=allocated_nodes_string) as co_mock:
 
738
                   return_value=allocated_nodes_string):
1151
739
            ips = account.get_allocated_ips()
1152
 
        co_mock.assert_called_once_with(
1153
 
            ('maas', 'mas', 'nodes', 'list-allocated'))
1154
740
        self.assertEqual({}, ips)
1155
741
 
1156
742
 
1157
 
class TestMAASAccountFromConfig(TestCase):
1158
 
 
1159
 
    def test_login_succeeds(self):
1160
 
        config = get_maas_env().config
1161
 
        with patch('subprocess.check_call', autospec=True) as cc_mock:
1162
 
            with maas_account_from_config(config) as maas:
1163
 
                self.assertIs(type(maas), MAASAccount)
1164
 
                self.assertEqual(maas.profile, 'mas')
1165
 
                self.assertEqual(maas.url, 'http://10.0.10.10/MAAS/api/2.0/')
1166
 
                self.assertEqual(maas.oauth, 'a:password:string')
1167
 
                # The login call has happened on context manager enter, reset
1168
 
                # the mock after to verify the logout call.
1169
 
                cc_mock.assert_called_once_with([
1170
 
                    'maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/2.0/',
1171
 
                    'a:password:string'])
1172
 
                cc_mock.reset_mock()
1173
 
        cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
1174
 
 
1175
 
    def test_login_fallback(self):
1176
 
        config = get_maas_env().config
1177
 
        login_error = CalledProcessError(1, ['maas', 'login'])
1178
 
        with patch('subprocess.check_call', autospec=True,
1179
 
                   side_effect=[login_error, None, None]) as cc_mock:
1180
 
            with maas_account_from_config(config) as maas:
1181
 
                self.assertIs(type(maas), MAAS1Account)
1182
 
                self.assertEqual(maas.profile, 'mas')
1183
 
                self.assertEqual(maas.url, 'http://10.0.10.10/MAAS/api/1.0/')
1184
 
                self.assertEqual(maas.oauth, 'a:password:string')
1185
 
                # The first login attempt was with the 2.0 api, after which
1186
 
                # a 1.0 login succeeded.
1187
 
                self.assertEquals(cc_mock.call_args_list, [
1188
 
                    call(['maas', 'login', 'mas',
1189
 
                          'http://10.0.10.10/MAAS/api/2.0/',
1190
 
                          'a:password:string']),
1191
 
                    call(['maas', 'login', 'mas',
1192
 
                          'http://10.0.10.10/MAAS/api/1.0/',
1193
 
                          'a:password:string']),
1194
 
                ])
1195
 
                cc_mock.reset_mock()
1196
 
        cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
1197
 
        self.assertEqual(
1198
 
            self.log_stream.getvalue(),
1199
 
            'INFO Could not login with MAAS 2.0 API, trying 1.0\n')
1200
 
 
1201
 
    def test_login_both_fail(self):
1202
 
        config = get_maas_env().config
1203
 
        login_error = CalledProcessError(1, ['maas', 'login'])
1204
 
        with patch('subprocess.check_call', autospec=True,
1205
 
                   side_effect=login_error) as cc_mock:
1206
 
            with self.assertRaises(CalledProcessError) as ctx:
1207
 
                with maas_account_from_config(config):
1208
 
                    self.fail('Should never get manager with failed login')
1209
 
        self.assertIs(ctx.exception, login_error)
1210
 
        self.assertEquals(cc_mock.call_args_list, [
1211
 
            call(['maas', 'login', 'mas',
1212
 
                  'http://10.0.10.10/MAAS/api/2.0/',
1213
 
                  'a:password:string']),
1214
 
            call(['maas', 'login', 'mas',
1215
 
                  'http://10.0.10.10/MAAS/api/1.0/',
1216
 
                  'a:password:string']),
1217
 
        ])
1218
 
        self.assertEqual(
1219
 
            self.log_stream.getvalue(),
1220
 
            'INFO Could not login with MAAS 2.0 API, trying 1.0\n')
1221
 
 
1222
 
 
1223
743
class TestMakeSubstrateManager(TestCase):
1224
744
 
1225
745
    def test_make_substrate_manager_aws(self):
1226
746
        aws_env = get_aws_env()
1227
 
        with make_substrate_manager(aws_env.config, aws_env.config) as aws:
 
747
        with make_substrate_manager(aws_env.config) as aws:
1228
748
            self.assertIs(type(aws), AWSAccount)
1229
749
            self.assertEqual(aws.euca_environ, {
1230
750
                'AWS_ACCESS_KEY': 'skeleton-key',
1237
757
 
1238
758
    def test_make_substrate_manager_openstack(self):
1239
759
        config = get_os_config()
1240
 
        with make_substrate_manager(config, config) as account:
 
760
        with make_substrate_manager(config) as account:
1241
761
            self.assertIs(type(account), OpenStackAccount)
1242
762
            self.assertEqual(account._username, 'foo')
1243
763
            self.assertEqual(account._password, 'bar')
1248
768
    def test_make_substrate_manager_rackspace(self):
1249
769
        config = get_os_config()
1250
770
        config['type'] = 'rackspace'
1251
 
        with make_substrate_manager(config, config) as account:
 
771
        with make_substrate_manager(config) as account:
1252
772
            self.assertIs(type(account), OpenStackAccount)
1253
773
            self.assertEqual(account._username, 'foo')
1254
774
            self.assertEqual(account._password, 'bar')
1258
778
 
1259
779
    def test_make_substrate_manager_joyent(self):
1260
780
        config = get_joyent_config()
1261
 
        with make_substrate_manager(config, config) as account:
 
781
        with make_substrate_manager(config) as account:
1262
782
            self.assertEqual(account.client.sdc_url, 'http://example.org/sdc')
1263
783
            self.assertEqual(account.client.account, 'user@manta.org')
1264
784
            self.assertEqual(account.client.key_id, 'key-id@manta.org')
1269
789
            'management-subscription-id': 'fooasdfbar',
1270
790
            'management-certificate': 'ab\ncd\n'
1271
791
            }
1272
 
        with make_substrate_manager(config, config) as substrate:
 
792
        with make_substrate_manager(config) as substrate:
1273
793
            self.assertIs(type(substrate), AzureAccount)
1274
794
            self.assertEqual(substrate.service_client.subscription_id,
1275
795
                             'fooasdfbar')
1277
797
                             'ab\ncd\n')
1278
798
        self.assertFalse(os.path.exists(substrate.service_client.cert_file))
1279
799
 
1280
 
    @patch('winazurearm.ARMClient.init_services',
1281
 
           autospec=True, side_effect=fake_init_services)
1282
 
    def test_make_substrate_manager_azure_arm(self, is_mock):
1283
 
        config = get_azure_config()
1284
 
        with make_substrate_manager(config, config) as substrate:
1285
 
            self.assertEqual(
1286
 
                substrate.arm_client.subscription_id, 'subscription-id')
1287
 
            self.assertEqual(
1288
 
                substrate.arm_client.client_id, 'application-id')
1289
 
            self.assertEqual(
1290
 
                substrate.arm_client.secret, 'application-password')
1291
 
            self.assertEqual(substrate.arm_client.tenant, 'tenant-id')
1292
 
            is_mock.assert_called_once_with(substrate.arm_client)
1293
 
 
1294
800
    def test_make_substrate_manager_other(self):
1295
801
        config = get_os_config()
1296
802
        config['type'] = 'other'
1297
 
        with make_substrate_manager(config, config) as account:
 
803
        with make_substrate_manager(config) as account:
1298
804
            self.assertIs(account, None)
1299
805
 
1300
806
 
1417
923
                        run_instances(2, 'qux', None)
1418
924
        co_mock.assert_called_once_with(
1419
925
            ['euca-run-instances', '-k', 'id_rsa', '-n', '2',
1420
 
             '-t', 'm3.large', '-g', 'manual-juju-test', ami],
 
926
             '-t', 'm1.large', '-g', 'manual-juju-test', ami],
1421
927
            env=os.environ)
1422
928
        cc_mock.assert_called_once_with(
1423
929
            ['euca-create-tags', '--tag', 'job_name=qux', 'i-foo', 'i-baz'],
1424
930
            env=os.environ)
1425
931
        di_mock.assert_called_once_with(['i-foo', 'i-baz'], env=os.environ)
1426
 
        qa_mock.assert_called_once_with('precise', 'amd64', region=None)
 
932
        qa_mock.assert_called_once_with('precise', 'amd64')
1427
933
 
1428
934
    @patch('get_ami.query_ami', autospec=True)
1429
935
    @patch('substrate.describe_instances', autospec=True)
1439
945
        di_mock.return_value = [('i-foo', 'bar-0'), ('i-baz', 'bar-1')]
1440
946
        qa_mock.return_value = "ami-atest"
1441
947
        run_instances(2, 'qux', 'wily')
1442
 
        qa_mock.assert_called_once_with('wily', 'amd64', region=None)
 
948
        qa_mock.assert_called_once_with('wily', 'amd64')
1443
949
 
1444
950
    def test_run_instances_tagging_failed(self):
1445
951
        euca_data = 'INSTANCE\ti-foo\tblah\tbar-0'