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)
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')])
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)
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')])
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']),
184
co_mock.assert_called_once_with(
185
('maas', 'mas', 'machine', 'release', 'node-3d'))
187
self.log_stream.getvalue(),
188
'INFO Deleting /A/B/C/D/node-3d/.\n')
155
['maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/1.0/',
156
'a:password:string'],
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')])
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)
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')])
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)
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')])
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)
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')])
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)
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')])
232
209
def test_terminate_joyent(self):
233
210
with patch('substrate.JoyentAccount.terminate_instances') as ti_mock:
275
242
self.assertEqual(aws.region, 'france')
244
def test_get_environ(self):
245
with AWSAccount.manager_from_config(get_aws_env().config) as aws:
246
environ = dict(os.environ)
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',
254
self.assertEqual(aws.get_environ(), environ)
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')
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')
277
274
def test_iter_security_groups(self):
281
def __init__(self, name):
282
self.name, self.id = name, name + "-id"
284
for name in ['foo', 'foobar', 'baz']:
285
group = FakeGroup(name)
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:
277
def make_group(name):
279
'GROUP', name + '-id', '689913858002',
280
name, 'juju group', 'vpc-1f40b47a'])
282
return_value = ''.join(
283
make_group(g) + '\n' for g in ['foo', 'foobar', 'baz'])
284
return_value += 'RANDOM\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)
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')])
303
295
def test_iter_instance_security_groups(self):
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'), ])]),
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:
299
MagicMock(instances=[MagicMock(groups=[
300
SecurityGroup(id='foo', name='bar'),
302
MagicMock(instances=[MagicMock(groups=[
303
SecurityGroup(id='baz', name='qux'),
304
SecurityGroup(id='quxx-id', name='quxx'),
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())
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)
322
315
def test_iter_instance_security_groups_instances(self):
324
MagicMock(instances=[MagicMock(groups=[
325
SecurityGroup(id='foo', name='bar'),
327
MagicMock(instances=[MagicMock(groups=[
328
SecurityGroup(id='baz', name='qux'),
329
SecurityGroup(id='quxx-id', name='quxx'),
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:
319
MagicMock(instances=[MagicMock(groups=[
320
SecurityGroup(id='foo', name='bar'),
322
MagicMock(instances=[MagicMock(groups=[
323
SecurityGroup(id='baz', name='qux'),
324
SecurityGroup(id='quxx-id', name='quxx'),
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'])
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, [])
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)
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:
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',
352
self.assertEqual(failures, ['foo', 'foobar', 'baz'])
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)
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
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]
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())
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())
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
403
connection.get_all_network_interfaces.return_value = [baz_interface]
404
return aws, baz_interface
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']))
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']))
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'])
428
427
def get_os_config():
689
655
client.delete_hosted_service.assert_called_once_with('foo')
692
def get_azure_config():
695
'subscription-id': 'subscription-id',
696
'application-id': 'application-id',
697
'application-password': 'application-password',
698
'tenant-id': 'tenant-id'
702
class TestAzureARMAccount(TestCase):
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:
710
substrate.arm_client.subscription_id, 'subscription-id')
711
self.assertEqual(substrate.arm_client.client_id, 'application-id')
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)
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)
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)
736
arm_client = ARMClient(
737
'subscription-id', 'application-id', 'application-password',
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'},
749
'model-uuid': 'baz', 'controller-uuid': 'bar'},
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)
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',
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'},
778
'model-uuid': 'baz', 'controller-uuid': 'bar'},
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)
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)
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)
813
class TestMAASAccount(TestCase):
815
def get_account(self):
816
"""Give a MAASAccount for testing."""
817
config = get_maas_env().config
819
config['name'], config['maas-server'], config['maas-oauth'])
821
@patch('subprocess.check_call', autospec=True)
822
def test_login(self, cc_mock):
823
account = self.get_account()
825
cc_mock.assert_called_once_with([
826
'maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/2.0/',
827
'a:password:string'])
829
@patch('subprocess.check_call', autospec=True)
830
def test_logout(self, cc_mock):
831
account = self.get_account()
833
cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
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'))
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'])
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'])
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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',
978
self.assertEqual({'id': 10}, interface)
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)
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)
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)
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)
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',
1033
self.assertEqual(None, result)
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)
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)
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)
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)
1089
class TestMAAS1Account(TestCase):
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'])
1097
@patch('subprocess.check_call', autospec=True)
1098
def test_login(self, cc_mock):
1099
account = self.get_account()
658
class TestMAASAcount(TestCase):
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)
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'])
1101
679
cc_mock.assert_called_once_with([
1102
680
'maas', 'login', 'mas', 'http://10.0.10.10/MAAS/api/1.0/',
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'])
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'])
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'])
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'])
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)
1157
class TestMAASAccountFromConfig(TestCase):
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'])
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']),
1195
cc_mock.reset_mock()
1196
cc_mock.assert_called_once_with(['maas', 'logout', 'mas'])
1198
self.log_stream.getvalue(),
1199
'INFO Could not login with MAAS 2.0 API, trying 1.0\n')
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']),
1219
self.log_stream.getvalue(),
1220
'INFO Could not login with MAAS 2.0 API, trying 1.0\n')
1223
743
class TestMakeSubstrateManager(TestCase):
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',