From 0f48d23996b7f7239470f730132c353f686afa3e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 15:10:56 +0530 Subject: [PATCH 0001/1050] Adding disaster recovery failover api --- .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ SoftLayer/CLI/routes.py | 2 + .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/storage.py | 11 ++++- docs/cli/block.rst | 4 ++ docs/cli/file.rst | 4 ++ tests/CLI/modules/block_tests.py | 39 +++++++++++++++++- tests/CLI/modules/file_tests.py | 40 ++++++++++++++++++- tests/managers/block_tests.py | 12 ++++++ tests/managers/file_tests.py | 12 ++++++ 11 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/CLI/block/replication/disaster_recovery_failover.py create mode 100644 SoftLayer/CLI/file/replication/disaster_recovery_failover.py diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..cc29fc0ac --- /dev/null +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = block_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..7fa02322a --- /dev/null +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original without support intervention\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = file_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -92,6 +92,7 @@ ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'), ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), + ('block:disaster-recovery-failover', 'SoftLayer.CLI.block.replication.disaster_recovery_failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), ('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'), ('block:replica-locations', 'SoftLayer.CLI.block.replication.locations:cli'), @@ -127,6 +128,7 @@ ('file:access-revoke', 'SoftLayer.CLI.file.access.revoke:cli'), ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'), ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'), + ('file:disaster-recovery-failover', 'SoftLayer.CLI.file.replication.disaster_recovery_failover:cli'), ('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'), ('file:replica-partners', 'SoftLayer.CLI.file.replication.partners:cli'), ('file:replica-locations', 'SoftLayer.CLI.file.replication.locations:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 37297e4d4..bf1f7adc4 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -221,6 +221,7 @@ failoverToReplicant = True failbackFromReplicant = True restoreFromSnapshot = True +disasterRecoveryFailoverToReplicant = True createSnapshot = { 'id': 449 diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 6aceb6f46..89955569c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -398,13 +398,22 @@ def failover_to_replicant(self, volume_id, replicant_id): """ return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): + """Disaster Recovery Failover to a volume replicant. + + :param integer volume_id: The id of the volume + :param integer replicant: ID of replicant to failover to + :return: Returns whether failover to successful or not + """ + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + def failback_from_replicant(self, volume_id): """Failback from a volume replicant. :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 5684b5623..18a324397 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -142,3 +142,7 @@ Block Commands .. click:: SoftLayer.CLI.block.set_note:cli :prog: block volume-set-note :show-nested: + +.. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli + :prog: block disaster-recovery-failover + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 31ceb0332..6c914b1a4 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -121,4 +121,8 @@ File Commands .. click:: SoftLayer.CLI.file.set_note:cli :prog: file volume-set-note + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli + :prog: file disaster-recovery-failover :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..88ad1480b 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerAPIError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -48,7 +49,7 @@ def test_volume_set_lun_id_in_range_missing_value(self): def test_volume_set_lun_id_not_in_range(self): value = '-1' lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId') - lun_mock.side_effect = exceptions.SoftLayerAPIError( + lun_mock.side_effect = SoftLayerAPIError( 'SoftLayer_Exception_Network_Storage_Iscsi_InvalidLunId', 'The LUN ID specified is out of the valid range: %s [min: 0 max: 4095]' % (value)) result = self.run_command('block volume-set-lun-id 1234 42'.split()) @@ -498,6 +499,18 @@ def test_replicant_failover(self): self.assert_no_fail(result) self.assertEqual('Failover to replicant is now in progress.\n', result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -558,6 +571,28 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dac95e0d8..dc50cd68c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -126,7 +127,7 @@ def test_volume_cancel_without_billing_item(self): result = self.run_command([ '--really', 'file', 'volume-cancel', '1234']) - self.assertIsInstance(result.exception, exceptions.SoftLayerError) + self.assertIsInstance(result.exception, SoftLayerError) def test_volume_detail(self): result = self.run_command(['file', 'volume-detail', '1234']) @@ -493,6 +494,41 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e603ef7e3..159553db7 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -384,6 +384,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.block.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 15d64d883..91f9325d5 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -289,6 +289,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From bf8c308eee0239accbccdfa1ea8221acb9ebe035 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 22:38:21 +0530 Subject: [PATCH 0002/1050] Refactoring the disaster recovery method --- .../block/replication/disaster_recovery_failover.py | 8 +++----- .../file/replication/disaster_recovery_failover.py | 8 +++----- tests/CLI/modules/block_tests.py | 12 ------------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cc29fc0ac..77b04c65b 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = block_storage_manager.disaster_recovery_failover_to_replicant( + block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 7fa02322a..2e2a946a1 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = file_storage_manager.disaster_recovery_failover_to_replicant( + file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 88ad1480b..6ccebf66f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -571,18 +571,6 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['block', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', - result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): confirm_mock.return_value = False From 47e49e56f4b8f0f9e65dad138145a75fa810350e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Thu, 7 Jan 2021 22:22:49 +0530 Subject: [PATCH 0003/1050] added warning message and summary for disaster-recover-failover --- .../replication/disaster_recovery_failover.py | 16 ++++++++++------ .../replication/disaster_recovery_failover.py | 14 +++++++++----- tests/CLI/modules/file_tests.py | 14 +------------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index 77b04c65b..cf79cd1a3 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. +To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') @@ -29,4 +33,4 @@ def cli(env, volume_id, replicant_id): replicant_id ) - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 2e2a946a1..3d175d909 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. +After using this method, to fail back to the original volume, please open a support ticket""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original without support intervention\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dc50cd68c..b13596355 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -503,19 +503,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) - - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') From 687678c1f32235fc099ffdedba3ee030e4c05df7 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 13 Jan 2021 18:28:06 +0530 Subject: [PATCH 0004/1050] addressing nitpicky --- .../replication/disaster_recovery_failover.py | 32 ++++++++++--------- .../replication/disaster_recovery_failover.py | 31 ++++++++++-------- SoftLayer/managers/storage.py | 3 +- tests/CLI/modules/block_tests.py | 13 ++++---- tests/CLI/modules/file_tests.py | 11 +++---- tests/managers/block_tests.py | 2 +- tests/managers/file_tests.py | 20 ++++++------ 7 files changed, 56 insertions(+), 56 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cf79cd1a3..1a0c304d8 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -1,36 +1,38 @@ -"""Failover an inaccessible file volume to its available replicant volume.""" +"""Failover an inaccessible block volume to its available replicant volume.""" # :license: MIT, see LICENSE for more details. import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. -To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") +@click.command(epilog="""Failover an inaccessible block volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env def cli(env, volume_id, replicant_id): - """Failover an inaccessible file volume to its available replicant volume.""" + """Failover an inaccessible block volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible block volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 3d175d909..a3f9373f7 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -4,14 +4,16 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. -After using this method, to fail back to the original volume, please open a support ticket""") +@click.command(epilog="""Failover an inaccessible file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover. +""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -19,18 +21,19 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 89955569c..44e76c138 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -405,7 +405,7 @@ def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): :param integer replicant: ID of replicant to failover to :return: Returns whether failover to successful or not """ - return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) def failback_from_replicant(self, volume_id): """Failback from a volume replicant. @@ -413,7 +413,6 @@ def failback_from_replicant(self, volume_id): :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 6ccebf66f..cbf1ba25f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerAPIError from SoftLayer import testing -from SoftLayer.CLI import exceptions + import json import mock @@ -497,9 +498,8 @@ def test_replicant_failover(self): '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Failover to replicant is now in progress.\n', - result.output) - + self.assertEqual('Failover to replicant is now in progress.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): @@ -509,8 +509,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -579,7 +578,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index b13596355..1bfe58e16 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,9 +4,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -from SoftLayer.CLI import exceptions import json import mock @@ -499,12 +499,10 @@ def test_replicant_failover_unsuccessful(self, failover_mock): def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): confirm_mock.return_value = True disaster_recovery_failover_mock.return_value = True - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): @@ -514,8 +512,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 159553db7..8dc1cd83b 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -394,7 +394,7 @@ def test_disaster_recovery_failover(self): 'disasterRecoveryFailoverToReplicant', args=(5678,), identifier=1234, - ) + ) def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 91f9325d5..11e35c001 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -290,16 +290,16 @@ def test_replicant_failover(self): ) def test_disaster_recovery_failover(self): - result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) - - self.assertEqual( - SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) - self.assert_called_with( - 'SoftLayer_Network_Storage', - 'disasterRecoveryFailoverToReplicant', - args=(5678,), - identifier=1234, - ) + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From 461cee479e078ed7b10094ee6758b438944a6188 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 4 Feb 2021 18:28:11 -0400 Subject: [PATCH 0005/1050] Add slcli vs create by router data. --- SoftLayer/CLI/virt/create.py | 6 ++++ SoftLayer/managers/vs.py | 39 +++++++++++++++++++-- tests/CLI/modules/vs/vs_create_tests.py | 43 +++++++++++++++++++++++ tests/managers/vs/vs_tests.py | 45 +++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index d9864a890..23a18457d 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -95,6 +95,8 @@ def _parse_create_args(client, args): "private_vlan": args.get('vlan_private', None), "public_subnet": args.get('subnet_public', None), "private_subnet": args.get('subnet_private', None), + "public_router": args.get('router_public', None), + "private_router": args.get('router_private', None), } # The primary disk is included in the flavor and the local_disk flag is not needed @@ -192,6 +194,10 @@ def _parse_create_args(client, args): help="The ID of the public SUBNET on which you want the virtual server placed") @click.option('--subnet-private', type=click.INT, help="The ID of the private SUBNET on which you want the virtual server placed") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--public-security-group', '-S', help=('Security group ID to associate with the public interface')) @helpers.multi_option('--private-security-group', '-s', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 1a03db1c6..3c3eab3a2 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,6 +470,7 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, + public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -533,6 +534,15 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} + if private_router or public_router: + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + private_router, public_router) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) @@ -581,7 +591,8 @@ def _generate_create_dict( def _create_network_components( self, public_vlan=None, private_vlan=None, - private_subnet=None, public_subnet=None): + private_subnet=None, public_subnet=None, + private_router=None, public_router=None): parameters = {} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} @@ -598,6 +609,12 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} + return parameters @retry(logger=LOGGER) @@ -685,7 +702,21 @@ def verify_create_instance(self, **kwargs): kwargs.pop('tags', None) create_options = self._generate_create_dict(**kwargs) template = self.guest.generateOrderTemplate(create_options) - if 'private_subnet' in kwargs or 'public_subnet' in kwargs: + if kwargs.get('public_router') or kwargs.get('private_router'): + if kwargs.get('private_vlan') or kwargs.get('public_vlan') or kwargs.get('private_subnet') \ + or kwargs.get('public_subnet'): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + vsi = template['virtualGuests'][0] + network_components = self._create_network_components(kwargs.get('public_vlan', None), + kwargs.get('private_vlan', None), + kwargs.get('private_subnet', None), + kwargs.get('public_subnet', None), + kwargs.get('private_router', None), + kwargs.get('public_router', None)) + vsi.update(network_components) + + if kwargs.get('private_subnet') or kwargs.get('public_subnet'): vsi = template['virtualGuests'][0] network_components = self._create_network_components(kwargs.get('public_vlan', None), kwargs.get('private_vlan', None), @@ -693,6 +724,9 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) + print("template") + print(template) + return template def create_instance(self, **kwargs): @@ -1121,6 +1155,7 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') + print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 53c3bdc97..2ad0f8647 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -93,6 +93,49 @@ def test_create_vlan_subnet(self, confirm_mock): self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_by_router(self, confirm_mock): + confirm_mock.return_value = True + + result = self.run_command(['vs', 'create', + '--cpu=2', + '--domain=example.com', + '--hostname=host', + '--os=UBUNTU_LATEST', + '--memory=1', + '--billing=hourly', + '--datacenter=dal05', + '--router-private=577940', + '--router-public=1639255', + '--tag=dev', + '--tag=green']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + args = ({ + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 577940 + } + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) + + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_with_wait_ready(self, confirm_mock): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index e858b2577..db8bb4ed8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -557,6 +557,38 @@ def test_generate_private_vlan(self): self.assertEqual(data, assert_data) + def test_generate_by_router_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + + def test_generate_by_router_and_subnet(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_subnet=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_generate_sec_group(self): data = self.vs._generate_create_dict( cpus=1, @@ -596,6 +628,19 @@ def test_create_network_components_vlan_subnet_private_vlan_subnet_public(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers(self): + data = self.vs._create_network_components( + private_router=1, + public_router=1 + ) + + assert_data = { + 'primaryBackendNetworkComponent': {'router': {'id': 1}}, + 'primaryNetworkComponent': {'router': {'id': 1}}, + } + + self.assertEqual(data, assert_data) + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From b66b563e0dfb14bebb1ab4497493babd507e9fb4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 14:47:37 -0400 Subject: [PATCH 0006/1050] Fix tox analysis. --- SoftLayer/managers/vs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 3c3eab3a2..20068ef61 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,7 +470,6 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, - public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -534,13 +533,14 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if private_router or public_router: + if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " "only router, not all options") network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet, - private_router, public_router) + kwargs.get('private_router'), + kwargs.get('public_router')) data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: From 9a0f48a264a293df2233eae4e88878954d8de432 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:04:05 -0400 Subject: [PATCH 0007/1050] Fix tox analysis. --- SoftLayer/managers/vs.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 20068ef61..6de17d0dd 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,20 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -589,6 +576,21 @@ def _generate_create_dict( return data + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + if kwargs.get('private_router') or kwargs.get('public_router'): + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet) + data.update(network_components) + def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, @@ -724,9 +726,6 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) - print("template") - print(template) - return template def create_instance(self, **kwargs): @@ -1155,7 +1154,6 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') - print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: From fde0cf6c0566f5e4f5a3e364bf53c257c58f3cbc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:37:51 -0400 Subject: [PATCH 0008/1050] Add method docstring. --- SoftLayer/managers/vs.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6de17d0dd..01c2b05e5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,10 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, + public_vlan) + + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,7 +579,16 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + """Get the network components structure. + + :param kwargs: Vs item list. + :param int private_subnet: Private subnet id. + :param int private_vlan: Private vlan id. + :param int public_subnet: Public subnet id. + :param int public_vlan: Public vlan id. + """ + network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -585,11 +597,12 @@ def get_network_components(self, data, kwargs, private_subnet, private_vlan, pub private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - data.update(network_components) + + return network_components def _create_network_components( self, public_vlan=None, private_vlan=None, From 6d354ce3de711ca0bab1443bcb87662220caeb21 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 16:09:10 -0400 Subject: [PATCH 0009/1050] Fix tox test issues. --- SoftLayer/managers/vs.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 01c2b05e5..0994530b3 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,10 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, - public_vlan) - - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -579,16 +576,16 @@ def _generate_create_dict( return data - def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): """Get the network components structure. + :param data: Array variable to add the network structure. :param kwargs: Vs item list. :param int private_subnet: Private subnet id. :param int private_vlan: Private vlan id. :param int public_subnet: Public subnet id. :param int public_vlan: Public vlan id. """ - network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -597,12 +594,11 @@ def get_network_components(self, kwargs, private_subnet, private_vlan, public_su private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - + data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - - return network_components + data.update(network_components) def _create_network_components( self, public_vlan=None, private_vlan=None, From b2f1af63973a4e79995337b020369cbfd6770aaf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Feb 2021 14:23:14 -0400 Subject: [PATCH 0010/1050] Refactor network components method. --- SoftLayer/managers/vs.py | 44 +++++++++++------------------------ tests/managers/vs/vs_tests.py | 12 ++++++++++ 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0994530b3..b247e8be5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,11 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,35 +580,19 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): - """Get the network components structure. - - :param data: Array variable to add the network structure. - :param kwargs: Vs item list. - :param int private_subnet: Private subnet id. - :param int private_vlan: Private vlan id. - :param int public_subnet: Public subnet id. - :param int public_vlan: Public vlan id. - """ - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) - def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, private_router=None, public_router=None): parameters = {} + if any([private_router, public_router]) and any([private_vlan, public_vlan, private_subnet, public_subnet]): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} if public_vlan: @@ -620,12 +608,6 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} - if private_router: - parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} - - if public_router: - parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} - return parameters @retry(logger=LOGGER) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index db8bb4ed8..c75e5e3b9 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -641,6 +641,18 @@ def test_create_network_components_by_routers(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._create_network_components, + private_router=1, + public_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From fc1f67ea245ce08ebfbb9bffd9293271ea07841f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:02:25 -0400 Subject: [PATCH 0011/1050] Add IOPs data to block volume list. --- SoftLayer/CLI/block/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 44489f928..769aad233 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -23,7 +23,7 @@ mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"), - column_helper.Column('iops', ('iops',), mask="iops"), + column_helper.Column('IOPs', ('provisionedIops',), mask="provisionedIops"), column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',), mask="serviceResourceBackendIpAddress"), column_helper.Column('lunId', ('lunId',), mask="lunId"), @@ -44,7 +44,7 @@ 'storage_type', 'capacity_gb', 'bytes_used', - 'iops', + 'IOPs', 'ip_addr', 'lunId', 'active_transactions', From e30066a08419ee9c9f38a4bdaaad2422771189eb Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:09:23 -0400 Subject: [PATCH 0012/1050] Add IOPs data to block volume-list unit test. --- tests/CLI/modules/block_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..97feb58be 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -130,7 +130,7 @@ def test_volume_list(self): 'capacity_gb': 20, 'datacenter': 'dal05', 'id': 100, - 'iops': None, + 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, 'notes': "{'status': 'availabl", From 2fae9ff48be806122ae79d85f62182582d622765 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:18:48 -0400 Subject: [PATCH 0013/1050] add a flags in the report bandwidth --- SoftLayer/CLI/report/bandwidth.py | 66 +++++--- tests/CLI/modules/report_tests.py | 250 ++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 23d1a157c..bd651c90a 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -176,7 +176,7 @@ def _get_virtual_bandwidth(env, start, end): '--start', callback=_validate_datetime, default=( - datetime.datetime.now() - datetime.timedelta(days=30) + datetime.datetime.now() - datetime.timedelta(days=30) ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( @@ -187,8 +187,12 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) +@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', + default=False) +@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', + default=False) @environment.pass_env -def cli(env, start, end, sortby): +def cli(env, start, end, sortby, virtual, server): """Bandwidth report for every pool/server. This reports on the total data transfered for each virtual sever, hardware @@ -213,24 +217,46 @@ def f_type(key, results): return (result['counter'] for result in results if result['type'] == key) - try: + def _input_to_table(item): + "Input metric data to table" + pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) + pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) + pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) + pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) + table.add_row([ + item['type'], + item['name'], + formatting.b_to_gb(pub_in), + formatting.b_to_gb(pub_out), + formatting.b_to_gb(pri_in), + formatting.b_to_gb(pri_out), + item.get('pool') or formatting.blank(), + ]) + + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing virtual collected results and then aborting.") + + elif server: + try: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing server collected results and then aborting.") + else: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end)): - pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) - pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) - pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) - pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) - table.add_row([ - item['type'], - item['name'], - formatting.b_to_gb(pub_in), - formatting.b_to_gb(pub_out), - formatting.b_to_gb(pri_in), - formatting.b_to_gb(pri_out), - item.get('pool') or formatting.blank(), - ]) - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 3d580edad..896e61e20 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -194,3 +194,253 @@ def test_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_virtual_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + guests = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + guests.return_value = [{ + 'id': 201, + 'metricTrackingObjectId': 201, + 'hostname': 'host1', + }, { + 'id': 202, + 'hostname': 'host2', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 203, + 'metricTrackingObjectId': 203, + 'hostname': 'host3', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }] + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--virtual', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }, { + 'hostname': 'host3', + 'pool': 2, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) + + def test_server_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + hardware = self.set_mock('SoftLayer_Account', 'getHardware') + hardware.return_value = [{ + 'id': 101, + 'metricTrackingObject': {'id': 101}, + 'hostname': 'host1', + }, { + 'id': 102, + 'hostname': 'host2', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 103, + 'metricTrackingObject': {'id': 103}, + 'hostname': 'host3', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }] + + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--server', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, { + 'hostname': 'host3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, ], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=103) + + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) From ecb46a514f1c295ff6595bdd7edc5e3650dc0e92 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:40:33 -0400 Subject: [PATCH 0014/1050] fix the tox tool --- tests/CLI/modules/report_tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 896e61e20..8489aeeab 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -102,7 +102,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'pool3', 'pool': None, @@ -110,7 +110,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'host1', 'pool': None, @@ -118,15 +118,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host3', - 'pool': 2, + 'pool': None, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host1', 'pool': None, @@ -134,16 +134,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', + 'type': 'virtual' }, { 'hostname': 'host3', - 'pool': None, + 'pool': 2, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', - }], + 'type': 'virtual'}], json.loads(stripped_output), ) self.assertEqual( From ea5c5c9f31c1b6be116a6c1ae10c35414b2c5ee9 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 12 Feb 2021 20:29:26 -0400 Subject: [PATCH 0015/1050] #1400 add 2FA and classic APIKeys fields to user list as default values --- SoftLayer/CLI/user/list.py | 19 ++++++++++++++++--- SoftLayer/fixtures/SoftLayer_Account.py | 8 ++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 142f824ab..10ba388a2 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -8,6 +8,8 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +TWO_FACTO_AUTH = 'externalBindingCount' +CLASSIC_API_KEYS = 'apiAuthenticationKeyCount' COLUMNS = [ column_helper.Column('id', ('id',)), @@ -17,15 +19,17 @@ column_helper.Column('status', ('userStatus', 'name')), column_helper.Column('hardwareCount', ('hardwareCount',)), column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), - column_helper.Column('2FAs', ('externalBindingCount',)), - column_helper.Column('classicAPIKeys', ('apiAuthenticationKeyCount',)) + column_helper.Column('2FA', (TWO_FACTO_AUTH,)), + column_helper.Column('classicAPIKey', (CLASSIC_API_KEYS,)) ] DEFAULT_COLUMNS = [ 'id', 'username', 'email', - 'displayName' + 'displayName', + '2FA', + 'classicAPIKey', ] @@ -44,7 +48,16 @@ def cli(env, columns): table = formatting.Table(columns.columns) for user in users: + user = _yes_format(user, [TWO_FACTO_AUTH, CLASSIC_API_KEYS]) table.add_row([value or formatting.blank() for value in columns.row(user)]) env.fout(table) + + +def _yes_format(user, keys): + """Changes all dictionary values to yes whose keys are in the list. """ + for key in keys: + if user.get(key): + user[key] = 'yes' + return user diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index a1c667cf1..123e0bc47 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -691,13 +691,17 @@ 'id': 11100, 'userStatus': {'name': 'Active'}, 'username': 'SL1234', - 'virtualGuestCount': 99}, + 'virtualGuestCount': 99, + 'externalBindingCount': 1, + 'apiAuthenticationKeyCount': 1, + }, {'displayName': 'PulseL', 'hardwareCount': 100, 'id': 11111, 'userStatus': {'name': 'Active'}, 'username': 'sl1234-abob', - 'virtualGuestCount': 99} + 'virtualGuestCount': 99, + } ] getReservedCapacityGroups = [ From ac7465fe94e35dee8f256f2aa97eb08669336fd5 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 15:35:12 -0400 Subject: [PATCH 0016/1050] Add the option network component by router to slcli hw create. --- SoftLayer/CLI/hardware/create.py | 8 ++++++- SoftLayer/managers/hardware.py | 8 ++++++- tests/CLI/modules/server_tests.py | 20 ++++++++++++++++ tests/managers/hardware_tests.py | 38 +++++++++++-------------------- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 40fa871bc..a1d373e14 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -31,6 +31,10 @@ help="Exports options to a template file") @click.option('--wait', type=click.INT, help="Wait until the server is finished provisioning for up to X seconds before returning") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") @helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env @@ -57,7 +61,9 @@ def cli(env, **args): 'port_speed': args.get('port_speed'), 'no_public': args.get('no_public') or False, 'extras': args.get('extra'), - 'network': args.get('network') + 'network': args.get('network'), + 'public_router': args.get('router_public', None), + 'private_router': args.get('router_private', None) } # Do not create hardware server with --test or --export diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..ecf1a89c2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -492,7 +492,9 @@ def _generate_create_dict(self, hourly=True, no_public=False, extras=None, - network=None): + network=None, + public_router=None, + private_router=None): """Translates arguments into a dictionary for creating a server.""" extras = extras or [] @@ -535,6 +537,10 @@ def _generate_create_dict(self, 'domain': domain, }] } + if private_router: + extras['hardware'][0]['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + extras['hardware'][0]['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if post_uri: extras['provisionScripts'] = [post_uri] diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..5a2db4fc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -418,6 +418,26 @@ def test_create_server_with_export(self, export_mock): self.assertIn("Successfully exported options to a template file.", result.output) export_mock.assert_called_once() + @mock.patch('SoftLayer.HardwareManager.place_order') + def test_create_server_with_router(self, order_mock): + order_mock.return_value = { + 'orderId': 98765, + 'orderDate': '2013-08-02 15:23:47' + } + + result = self.run_command(['--really', 'server', 'create', + '--size=S1270_8GB_2X1TBSATA_NORAID', + '--hostname=test', + '--domain=example.com', + '--datacenter=TEST00', + '--port-speed=100', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + '--router-private=123', + '--router-public=1234' + ]) + + self.assert_no_fail(result) + def test_edit_server_userdata_and_file(self): # Test both userdata and userfile at once with tempfile.NamedTemporaryFile() as userfile: diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..fb8b734ea 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -318,41 +318,29 @@ def test_generate_create_dict(self): 'port_speed': 10, 'hourly': True, 'extras': ['1_IPV6_ADDRESS'], - 'post_uri': 'http://example.com/script.php', - 'ssh_keys': [10], + 'public_router': 1111, + 'private_router': 1234 } - package = 'BARE_METAL_SERVER' - location = 'wdc07' - item_keynames = [ - '1_IP_ADDRESS', - 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', - 'REBOOT_KVM_OVER_IP', - 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'BANDWIDTH_0_GB_2', - '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', - '1_IPV6_ADDRESS' - ] - hourly = True - preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' extras = { 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', - }], - 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys': [{'sshKeyIds': [10]}] + 'primaryNetworkComponent': { + "router": { + "id": 1111 + } + }, + 'primaryBackendNetworkComponent': { + "router": { + "id": 1234 + } + } + }] } data = self.hardware._generate_create_dict(**args) - - self.assertEqual(package, data['package_keyname']) - self.assertEqual(location, data['location']) - for keyname in item_keynames: - self.assertIn(keyname, data['item_keynames']) self.assertEqual(extras, data['extras']) - self.assertEqual(preset_keyname, data['preset_keyname']) - self.assertEqual(hourly, data['hourly']) def test_generate_create_dict_network_key(self): args = { From 5a046cbea66e640e3fadd8811b00170ab6708f2c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 16:01:50 -0400 Subject: [PATCH 0017/1050] Add unit test. --- tests/managers/hardware_tests.py | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index fb8b734ea..c709994b4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -309,6 +309,52 @@ def test_generate_create_dict_no_regions(self): self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) def test_generate_create_dict(self): + args = { + 'size': 'S1270_8GB_2X1TBSATA_NORAID', + 'hostname': 'unicorn', + 'domain': 'giggles.woo', + 'location': 'wdc07', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'port_speed': 10, + 'hourly': True, + 'extras': ['1_IPV6_ADDRESS'], + 'post_uri': 'http://example.com/script.php', + 'ssh_keys': [10], + } + + package = 'BARE_METAL_SERVER' + location = 'wdc07' + item_keynames = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP', + 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'BANDWIDTH_0_GB_2', + '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + '1_IPV6_ADDRESS' + ] + hourly = True + preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' + extras = { + 'hardware': [{ + 'domain': 'giggles.woo', + 'hostname': 'unicorn', + }], + 'provisionScripts': ['http://example.com/script.php'], + 'sshKeys': [{'sshKeyIds': [10]}] + } + + data = self.hardware._generate_create_dict(**args) + + self.assertEqual(package, data['package_keyname']) + self.assertEqual(location, data['location']) + for keyname in item_keynames: + self.assertIn(keyname, data['item_keynames']) + self.assertEqual(extras, data['extras']) + self.assertEqual(preset_keyname, data['preset_keyname']) + self.assertEqual(hourly, data['hourly']) + + def test_generate_create_dict_by_router_network_component(self): args = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', From f20508e44c3dd771be294df2aa2ef8709038c131 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Feb 2021 18:13:46 -0400 Subject: [PATCH 0018/1050] Fix team code review comments --- SoftLayer/CLI/report/bandwidth.py | 39 ++++++++++++------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index bd651c90a..5a29bf192 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -187,9 +187,9 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', +@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', +@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): @@ -233,30 +233,21 @@ def _input_to_table(item): item.get('pool') or formatting.blank(), ]) - if virtual: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing virtual collected results and then aborting.") - - elif server: - try: + try: + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + elif server: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end)): _input_to_table(item) - except KeyboardInterrupt: - env.err("Printing server collected results and then aborting.") - else: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + else: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) From 81afeafacb574682798b80b1a3a4b9761857cc51 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 23 Feb 2021 22:15:03 -0400 Subject: [PATCH 0019/1050] fix the tox tool --- SoftLayer/CLI/custom_types.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index 66167b68a..c5a2102a2 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -18,7 +18,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): + def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/setup.py b/setup.py index 2cb688c44..ad934b774 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION - +# pylint: disable=inconsistent-return-statements setup( name='SoftLayer', version='5.9.2', From 8bf29e4e0e9c869853c778a8b09fa9715d181b06 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 09:53:08 -0400 Subject: [PATCH 0020/1050] fix tox tool --- SoftLayer/CLI/custom_types.py | 3 ++- SoftLayer/CLI/report/bandwidth.py | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index c5a2102a2..e0f27b042 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -9,6 +9,7 @@ import click +# pylint: disable=inconsistent-return-statements class NetworkParamType(click.ParamType): """Validates a network parameter type and converts to a tuple. @@ -18,7 +19,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements + def convert(self, value, param, ctx): try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 5a29bf192..4ae2d0f68 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -175,9 +175,8 @@ def _get_virtual_bandwidth(env, start, end): @click.option( '--start', callback=_validate_datetime, - default=( - datetime.datetime.now() - datetime.timedelta(days=30) - ).strftime('%Y-%m-%d'), + default=(datetime.datetime.now() - datetime.timedelta(days=30) + ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( '--end', @@ -187,9 +186,11 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', +@click.option('--virtual', is_flag=True, + help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', +@click.option('--server', is_flag=True, + help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): From a172c908d4485ade7320f230769750aaa6afd780 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:10:37 -0400 Subject: [PATCH 0021/1050] fix tox tool --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ad934b774..ef532573a 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION -# pylint: disable=inconsistent-return-statements + setup( name='SoftLayer', version='5.9.2', @@ -55,4 +55,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) +) # pylint: disable=inconsistent-return-statements From ab6962465aa11abc1cda02c0bb326858fad2c3db Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:29:24 -0400 Subject: [PATCH 0022/1050] fix tox tool --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef532573a..2591b055a 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ from setuptools import setup, find_packages +# pylint: disable=inconsistent-return-statements + DESCRIPTION = "A library for SoftLayer's API" if os.path.exists('README.rst'): @@ -55,4 +57,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) # pylint: disable=inconsistent-return-statements +) From 427294d8e2cc8a5ea6ee8abb544f088734ea25d3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 16:57:38 -0400 Subject: [PATCH 0023/1050] fix tox tool --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 9b1259891..7f1833a4a 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -12,7 +12,7 @@ from SoftLayer.CLI import formatting -def get_api_key(client, username, secret): +def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. This will also generate an API key if one doesn't exist From 2afcd9d26901c20e7d60be97fd48d73e4f1c34f0 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 10:36:29 -0400 Subject: [PATCH 0024/1050] Allow modifying Timeout for LoadBalancers --- SoftLayer/CLI/loadbal/pools.py | 3 +++ SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 ++ tests/CLI/modules/loadbal_tests.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index 148395cd2..bfd1d48f7 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -84,6 +84,8 @@ def add(env, identifier, **args): @click.option('--method', '-m', help="Balancing Method", type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") +@click.option('--clientTimeout', '-t', type=int, + help="maximum idle time in seconds(Range: 1 to 7200).") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") @click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env @@ -108,6 +110,7 @@ def edit(env, identifier, listener, **args): 'method': 'loadBalancingMethod', 'connections': 'maxConn', 'sticky': 'sessionType', + 'clienttimeout': 'clientTimeout', 'sslcert': 'tlsCertificateId' } diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 94220cdea..52158f620 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -48,6 +48,7 @@ ], 'listeners': [ { + 'clientTimeout': 15, 'defaultPool': { 'healthMonitor': { 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c8' @@ -97,6 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, + 'clientTimeout': 25, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 84866d3f5..b2da4c374 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -49,7 +49,7 @@ def test_delete_pool(self): def test_edit_pool(self): result = self.run_command(['loadbal', 'pool-edit', '111111', '370a9f12-b3be-47b3-bfa5-8e460010000', '-f 510', - '-b 256', '-c 5']) + '-b 256', '-c 5', '-t 10']) self.assert_no_fail(result) def test_add_7p(self): From 5d88288c712e2eb494665c26aee828f8103ca809 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 17:37:59 -0400 Subject: [PATCH 0025/1050] updating --- SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 52158f620..70e8b64cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -98,7 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, - 'clientTimeout': 25, + 'clientTimeout': 30, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ From 956d7fa6baef8b5c584663b488f29ff750d6dbfc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 16:55:39 -0600 Subject: [PATCH 0026/1050] #1425 checking termLength when ordering --- SoftLayer/managers/ordering.py | 11 ++++++--- tests/managers/ordering_tests.py | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..dcfb4e186 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -406,11 +406,16 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): return prices @staticmethod - def get_item_price_id(core, prices): - """get item price id""" + def get_item_price_id(core, prices, term=0): + """get item price id + + core: None or a number to match against capacityRestrictionType + prices: list of SoftLayer_Product_Item_Price + term: int to match against SoftLayer_Product_Item_Price.termLength + """ price_id = None for price in prices: - if not price['locationGroupId']: + if not price['locationGroupId'] and price.get('termLength', 0) in {term, '', None}: restriction = price.get('capacityRestrictionType', False) # There is a price restriction. Make sure the price is within the restriction if restriction and core is not None: diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..48cf38735 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -841,3 +841,45 @@ def test_resolve_location_name_invalid(self): def test_resolve_location_name_not_exist(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) + + # https://github.com/softlayer/softlayer-python/issues/1425 + # Issues relating to checking prices based of the price.term relationship + def test_issues1425_zeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 0 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test None termLength + price2['termLength'] = None + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test '' termLength + price2['termLength'] = '' + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + def test_issues1425_nonzeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 36 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) + self.assertEqual(1234, price_id) + + # Test None-existing price for term + price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) + self.assertEqual(None, price_id) \ No newline at end of file From 3b6aae8ca03a8378cb3a0697480f0fde24e0d6be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 17:01:07 -0600 Subject: [PATCH 0027/1050] tox fixes --- tests/managers/ordering_tests.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 48cf38735..7adcd684f 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -847,11 +847,11 @@ def test_resolve_location_name_not_exist(self): def test_issues1425_zeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 0 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1]) @@ -870,11 +870,11 @@ def test_issues1425_zeroterm(self): def test_issues1425_nonzeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 36 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) @@ -882,4 +882,4 @@ def test_issues1425_nonzeroterm(self): # Test None-existing price for term price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) - self.assertEqual(None, price_id) \ No newline at end of file + self.assertEqual(None, price_id) From b9c759566b2ecddafc4730b47dfa3fc885f00a85 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 19:51:20 -0600 Subject: [PATCH 0028/1050] Add testing and support for python 3.9. Remove testtools as a test dependency. It's not used except for a version skip that is easy to replace. testtools still relies on unittest2, a python 2-ism that generates warnings in python 3.9. Fix the snapcraft release by updating to a newer snapcraft action that fixes the 'add-path' error. --- .github/workflows/release.yml | 46 +++++++------- .github/workflows/tests.yml | 108 ++++++++++++++++---------------- README.rst | 4 +- setup.py | 1 + tests/CLI/modules/user_tests.py | 5 +- tools/test-requirements.txt | 1 - tox.ini | 2 +- 7 files changed, 84 insertions(+), 83 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36ef10414..9b244a86b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,23 @@ -name: Release - -on: - release: - types: [published] - -jobs: - release: - runs-on: ubuntu-latest - strategy: - matrix: - arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] - steps: - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1.1.1 - with: - snapcraft_token: ${{ secrets.snapcraft_token }} - - name: Push to stable - run: | - VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` - echo Publishing $VERSION on ${{ matrix.arch }} - snapcraft release slcli $VERSION stable - +name: Release + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-18.04 + strategy: + matrix: + arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] + steps: + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.2.0 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7aa408ed8..7bc787791 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,54 +1,54 @@ -name: Tests - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.5,3.6,3.7,3.8] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Test - run: tox -e py - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Coverage - run: tox -e coverage - analysis: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Analysis - run: tox -e analysis \ No newline at end of file +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5,3.6,3.7,3.8,3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Test + run: tox -e py + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Coverage + run: tox -e coverage + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Analysis + run: tox -e analysis diff --git a/README.rst b/README.rst index 4f741a5d0..75e5d6f54 100644 --- a/README.rst +++ b/README.rst @@ -127,7 +127,7 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 3.5, 3.6, 3.7, or 3.8. +* Python 3.5, 3.6, 3.7, 3.8, or 3.9. * A valid SoftLayer API username and key. * A connection to SoftLayer's private network is required to use our private network API endpoints. @@ -150,6 +150,6 @@ Python Packages Copyright --------- -This software is Copyright (c) 2016-2019 SoftLayer Technologies, Inc. +This software is Copyright (c) 2016-2021 SoftLayer Technologies, Inc. See the bundled LICENSE file for more information. diff --git a/setup.py b/setup.py index 2591b055a..7bbc3ebe5 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index b6723a2b2..246a5b0a2 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,6 @@ import sys import mock -import testtools from SoftLayer import testing @@ -168,10 +167,12 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @testtools.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): + if sys.version_info < (3, 6): + self.skipTest("Secrets module only exists in version 3.6+") + secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index d417e04c1..0f1ec684c 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,7 +4,6 @@ pytest pytest-cov mock sphinx -testtools ptable >= 0.9.2 click >= 7 requests >= 2.20.0 diff --git a/tox.ini b/tox.ini index c9b3b5e72..b1fa2c870 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,pypy3,analysis,coverage,docs +envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From f019f3dd68447b95aacce6978b86f21d2b72ddb3 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:12:20 -0600 Subject: [PATCH 0029/1050] Use testtools.SkipIf instead so that secrets mocking isn't broken for python 3.5. --- tests/CLI/modules/user_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 246a5b0a2..3693d32b5 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -6,6 +6,7 @@ """ import json import sys +import unittest import mock @@ -167,12 +168,10 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) + @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): - if sys.version_info < (3, 6): - self.skipTest("Secrets module only exists in version 3.6+") - secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) From 8d73349a740a9887ad7cc97acf2057b32aff58ea Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:16:17 -0600 Subject: [PATCH 0030/1050] Fix casing --- tests/CLI/modules/user_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 3693d32b5..2f4c1c978 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -168,7 +168,7 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") + @unittest.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): From fb138c6cc95a4c0eefcf0f393d67b04fb68c895d Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 2 Mar 2021 18:35:29 -0400 Subject: [PATCH 0031/1050] #1430 refactor Ordering verify_quote cleaning empty or None fields logic --- SoftLayer/managers/ordering.py | 6 +++--- tests/managers/ordering_tests.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..5faf8af60 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -218,11 +218,11 @@ def verify_quote(self, quote_id, extra): container = self.generate_order_template(quote_id, extra) clean_container = {} - # There are a few fields that wil cause exceptions in the XML endpoing if you send in '' - # reservedCapacityId and hostId specifically. But we clean all just to be safe. + # There are a few fields that wil cause exceptions in the XML endpoint if you send in '', + # or None in Rest endpoint (e.g. reservedCapacityId, hostId). But we clean all just to be safe. # This for some reason is only a problem on verify_quote. for key in container.keys(): - if container.get(key) != '': + if container.get(key): clean_container[key] = container[key] return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', clean_container, id=quote_id) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..e790028ba 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -713,7 +713,8 @@ def test_clean_quote_verify(self): 'hostname': 'test1', 'domain': 'example.com' }], - 'testProperty': '' + 'testPropertyEmpty': '', + 'testPropertyNone': None } result = self.ordering.verify_quote(1234, extras) @@ -721,7 +722,8 @@ def test_clean_quote_verify(self): self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder') call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder')[0] order_container = call.args[0] - self.assertNotIn('testProperty', order_container) + self.assertNotIn('testPropertyEmpty', order_container) + self.assertNotIn('testPropertyNone', order_container) self.assertNotIn('reservedCapacityId', order_container) def test_get_item_capacity_core(self): From e4c72b8562c8a82c3fa3adf0a26aa6ff8f8f0b8c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:24:42 -0400 Subject: [PATCH 0032/1050] Add routers for each DC in `slcli hw create-options` --- SoftLayer/CLI/hardware/create_options.py | 29 +++++++++++++++++++++++- SoftLayer/fixtures/SoftLayer_Account.py | 18 +++++++++++++++ SoftLayer/managers/account.py | 10 ++++++++ tests/managers/account_tests.py | 4 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 4c7723fb9..5231904cd 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -5,6 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers import account from SoftLayer.managers import hardware @@ -18,8 +19,18 @@ def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) + account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) + _filter = '' + if location: + _filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } + + routers = account_manager.get_routers(_filter) tables = [] # Datacenters @@ -34,6 +45,7 @@ def cli(env, prices, location=None): tables.append(_os_prices_table(options['operating_systems'], prices)) tables.append(_port_speed_prices_table(options['port_speeds'], prices)) tables.append(_extras_prices_table(options['extras'], prices)) + tables.append(_get_routers(routers)) # since this is multiple tables, this is required for a valid JSON object to be rendered. env.fout(formatting.listing(tables, separator='\n')) @@ -50,7 +62,7 @@ def _preset_prices_table(sizes, prices=False): for size in sizes: if size.get('hourlyRecurringFee', 0) + size.get('recurringFee', 0) + 1 > 0: table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) + "%.4f" % size['recurringFee']]) else: table = formatting.Table(['Size', 'Value'], title="Sizes") for size in sizes: @@ -146,3 +158,18 @@ def _get_price_data(price, item): if item in price: result = price[item] return result + + +def _get_routers(routers): + """Get all routers information + + :param routers: Routers data + """ + + table = formatting.Table(["id", "hostname", "name"], title='Routers') + for router in routers: + table.add_row([router['id'], + router['hostname'], + router['topLevelLocation']['longName'], ]) + table.align = 'l' + return table diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..3c82b3da3 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,21 @@ "resourceTableId": 777777 } ] + +getRouters = [ + { + "accountId": 1, + "bareMetalInstanceFlag": 0, + "domain": "softlayer.com", + "fullyQualifiedDomainName": "fcr01a.ams01.softlayer.com", + "hardwareStatusId": 5, + "hostname": "fcr01a.ams01", + "id": 123456, + "serviceProviderId": 1, + "topLevelLocation": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + } + }] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 45ac3ad52..6b8c9f031 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -276,3 +276,13 @@ def get_account_all_billing_orders(self, limit=100, mask=None): """ return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) + + def get_routers(self, mask=None, _filter=None): + """Gets all the routers currently active on the account + + :param string mask: Object Mask + :param string _filter: Object filter + :returns: Routers + """ + + return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index b051e5ee7..c5d2edf95 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -149,3 +149,7 @@ def test_get_item_details_with_invoice_item_id(self): self.manager.get_item_detail(123456) self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=123456) self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=123456) + + def test_get_routers(self): + self.manager.get_routers() + self.assert_called_with("SoftLayer_Account", "getRouters") From 78400f19bf15cbdb1cab499ff65548cb6d59629a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:47:52 -0400 Subject: [PATCH 0033/1050] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 12 +++--------- SoftLayer/managers/account.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 5231904cd..186a6fe44 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -22,15 +22,9 @@ def cli(env, prices, location=None): account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - _filter = '' - if location: - _filter = { - 'routers': { - 'topLevelLocation': {'name': {'operation': location}} - } - } - - routers = account_manager.get_routers(_filter) + + + routers = account_manager.get_routers(location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 6b8c9f031..d008b92a3 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,12 +277,19 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, _filter=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask - :param string _filter: Object filter + :param string location: location string :returns: Routers """ + object_filter = '' + if location: + object_filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } - return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) + return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) From 5d338a529322364c526e5ed9993214bf025439bc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 3 Mar 2021 15:50:21 -0600 Subject: [PATCH 0034/1050] v5.9.3 Release notes and updates --- CHANGELOG.md | 22 ++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86588d91..70ab66753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log + +## [5.9.3] - 2021-03-03 +https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 + +#### New Commands +- `slcli file|block disaster-recovery-failover` #1407 + +#### Improvements +- Unit testing for large integers #1403 +- Add Multi factor authentication to users list #1408 +- Add pagination to object storage list accounts. #1411 +- Add username lookup to slcli object-storage credential #1415 +- Add IOPs data to slcli block volume-list. #1418 +- Add 2FA and classic APIKeys fields to slcli user list as default values #1421 +- Add a flags in the report bandwidth #1420 +- Add the option network component by router to slcli hw create. #1422 +- Add slcli vs create by router data. #1414 +- Add testing and support for python 3.9. #1429 +- Checking for TermLength on prices #1428 + + + ## [5.9.2] - 2020-12-03 https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index a09e85706..b10b164d0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.2' +VERSION = 'v5.9.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 7bbc3ebe5..6d51d38ce 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.2', + version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From fa9da6a1dbd884212e65f8ba3a3564070e086b2c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:56:49 -0400 Subject: [PATCH 0035/1050] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 186a6fe44..7d104d877 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,9 +21,6 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - - - routers = account_manager.get_routers(location) tables = [] From f4fee95a9a83dd746388c9e37c9df1bc521febe7 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 9 Mar 2021 15:14:54 -0400 Subject: [PATCH 0036/1050] Add preset datatype in `slcli virtual detail` --- SoftLayer/CLI/virt/detail.py | 1 + SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 94c0c7994..731df19ea 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..5a3bcc105 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -28,7 +28,8 @@ 'userRecord': { 'username': 'chechu', } - } + }, + 'preset': {'keyName': 'B1_8X16X100'} } }, 'datacenter': {'id': 50, 'name': 'TEST00', @@ -480,16 +481,16 @@ "categoryCode": "guest_disk0", "id": 81 }}, { - "description": "250 GB (SAN)", - "attributes": [ - { - "id": 198, - "attributeTypeKeyName": "SAN_DISK" - }], - "itemCategory": { - "categoryCode": "guest_disk0", - "id": 89 - }}], + "description": "250 GB (SAN)", + "attributes": [ + { + "id": 198, + "attributeTypeKeyName": "SAN_DISK" + }], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 89 + }}], 'guest_core': [{ "description": "4 x 2.0 GHz or higher Cores (Dedicated)", "attributes": [], @@ -497,13 +498,13 @@ "categoryCode": "guest_core", "id": 80 }}, - { - "description": "8 x 2.0 GHz or higher Cores", - "attributes": [], - "itemCategory": { - "categoryCode": "guest_core", - "id": 90 - }}] + { + "description": "8 x 2.0 GHz or higher Cores", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 90 + }}] } getReverseDomainRecords = [{ From c400ab560033f9282634197bbc3fb1dd52633757 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:02:54 -0400 Subject: [PATCH 0037/1050] Adding upgrade option to slcli hw. Adding cli unit test. --- SoftLayer/CLI/hardware/upgrade.py | 47 ++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 76 ++++++++++ SoftLayer/managers/hardware.py | 139 ++++++++++++++++++ tests/CLI/modules/server_tests.py | 27 ++++ 5 files changed, 290 insertions(+) create mode 100644 SoftLayer/CLI/hardware/upgrade.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py new file mode 100644 index 000000000..d766000ab --- /dev/null +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -0,0 +1,47 @@ +"""Upgrade a hardware server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--memory', type=click.INT, help="Memory Size in GB") +@click.option('--network', help="Network port speed in Mbps", + default=None, + type=click.Choice(['100', '100 Redundant', '100 Dual', + '1000', '1000 Redundant', '1000 Dual', + '10000', '10000 Redundant', '10000 Dual']) + ) +@click.option('--drive-controller', + help="Drive Controller", + default=None, + type=click.Choice(['Non-RAID', 'RAID'])) +@click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") +@environment.pass_env +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): + """Upgrade a Hardware Server.""" + + mgr = SoftLayer.HardwareManager(env.client) + + if not any([memory, network, drive_controller, public_bandwidth]): + raise exceptions.ArgumentError("Must provide " + " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') + if not test: + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborted') + + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, test=test): + raise exceptions.CLIAbort('Hardware Server Upgrade Failed') + env.fout('Successfully Upgraded.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..eee8fe314 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), + ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 5c26d20da..a28d0fc13 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -6,6 +6,9 @@ 'billingItem': { 'id': 6327, 'recurringFee': 1.54, + 'package': { + 'id': 911 + }, 'nextInvoiceTotalRecurringAmount': 16.08, 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, @@ -262,3 +265,76 @@ } } } + +getUpgradeItemPrices = [ + { + "id": 21525, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + } + ], + "item": { + "capacity": "10000", + "description": "10 Gbps Redundant Public & Private Network Uplinks", + "id": 4342, + "keyName": "10_GBPS_REDUNDANT_PUBLIC_PRIVATE_NETWORK_UPLINKS" + } + }, + { + "hourlyRecurringFee": ".247", + "id": 209391, + "recurringFee": "164", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG" + } + }, + { + "hourlyRecurringFee": ".068", + "id": 22482, + "recurringFee": "50", + "categories": [ + { + "categoryCode": "disk_controller", + "id": 11, + "name": "Disk Controller", + } + ], + "item": { + "capacity": "0", + "description": "RAID", + "id": 4478, + "keyName": "DISK_CONTROLLER_RAID", + } + }, + { + "id": 50357, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + } + ], + "item": { + "capacity": "500", + "description": "500 GB Bandwidth Allotment", + "id": 6177, + "keyName": "BANDWIDTH_500_GB" + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..5eeac0709 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -5,11 +5,13 @@ :license: MIT, see LICENSE for more details. """ +import datetime import logging import socket import time from SoftLayer.decoration import retry +from SoftLayer import exceptions from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager @@ -780,6 +782,143 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def upgrade(self, instance_id, memory=None, + nic_speed=None, drive_controller=None, + public_bandwidth=None, test=False): + """Upgrades a hardware server instance. + + :param int instance_id: Instance id of the hardware server to be upgraded. + :param string memory: Memory size. + :param string nic_speed: Network Port Speed data. + :param string drive_controller: Drive Controller data. + :param string public_bandwidth: Public keyName data. + :param bool test: Test option to verify the request. + + :returns: bool + """ + upgrade_prices = self._get_upgrade_prices(instance_id) + prices = [] + data = {} + + if memory: + data['memory'] = memory + if nic_speed: + data['nic_speed'] = nic_speed + if drive_controller: + data['disk_controller'] = drive_controller + if public_bandwidth: + data['bandwidth'] = public_bandwidth + + server_response = self.get_instance(instance_id) + package_id = server_response['billingItem']['package']['id'] + + maintenance_window = datetime.datetime.now(utils.UTC()) + order = { + 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', + 'properties': [{ + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }], + 'hardware': [{'id': int(instance_id)}], + 'packageId': package_id + } + + for option, value in data.items(): + price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) + if not price_id: + # Every option provided is expected to have a price + raise exceptions.SoftLayerError( + "Unable to find %s option with value %s" % (option, value)) + + prices.append({'id': price_id}) + + order['prices'] = prices + + if prices: + if test: + self.client['Product_Order'].verifyOrder(order) + else: + self.client['Product_Order'].placeOrder(order) + return True + return False + + @retry(logger=LOGGER) + def get_instance(self, instance_id): + """Get details about a hardware server instance. + + :param int instance_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified instance. + """ + mask = [ + 'billingItem[id,package[id,keyName]]' + ] + mask = "mask[%s]" % ','.join(mask) + + return self.hardware.getObject(id=instance_id, mask=mask) + + def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + """Following Method gets all the price ids related to upgrading a VS. + + :param int instance_id: Instance id of the VS to be upgraded + + :returns: list + """ + mask = [ + 'id', + 'locationGroupId', + 'categories[name,id,categoryCode]', + 'item[keyName,description,capacity,units]' + ] + mask = "mask[%s]" % ','.join(mask) + return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + + @staticmethod + def _get_prices_for_upgrade_option(upgrade_prices, option, value): + """Find the price id for the option and value to upgrade. This + + :param list upgrade_prices: Contains all the prices related to a + hardware server upgrade. + :param string option: Describes type of parameter to be upgraded + :param value: The value of the parameter to be upgraded + """ + + option_category = { + 'memory': 'ram', + 'nic_speed': 'port_speed', + 'disk_controller': 'disk_controller', + 'bandwidth': 'bandwidth' + } + category_code = option_category.get(option) + + for price in upgrade_prices: + if price.get('categories') is None or price.get('item') is None: + continue + + product = price.get('item') + for category in price.get('categories'): + if not (category.get('categoryCode') == category_code): + continue + + if option == 'disk_controller': + if value == product.get('description'): + return price.get('id') + elif option == 'nic_speed': + if value.isdigit(): + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + split_nic_speed = value.split(" ") + if str(product.get('capacity')) == str(split_nic_speed[0]) and \ + split_nic_speed[1] in product.get("description"): + return price.get('id') + elif option == 'bandwidth': + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + if str(product.get('capacity')) == str(value): + return price.get('id') + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..efd7eb32b 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -883,3 +883,30 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_upgrade_no_options(self, ): + result = self.run_command(['hw', 'upgrade', '100']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.ArgumentError) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_aborted(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--memory=1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_test(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + From 8addedbf3225593590f205b559378ea1d6472554 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:38:20 -0400 Subject: [PATCH 0038/1050] Adding unit test. --- SoftLayer/managers/hardware.py | 4 ++-- tests/managers/hardware_tests.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5eeac0709..fc083995c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -788,10 +788,10 @@ def upgrade(self, instance_id, memory=None, """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. - :param string memory: Memory size. + :param int memory: Memory size. :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. - :param string public_bandwidth: Public keyName data. + :param int public_bandwidth: Public keyName data. :param bool test: Test option to verify the request. :returns: bool diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..e44301c73 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -807,6 +807,47 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_get_price_id_memory_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(99, result) + + def test_get_price_id_mismatch_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity': 1}, 'id': 90}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 2}, 'id': 91}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 92}, + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(92, result) + + def test_upgrade(self): + result = self.hardware.upgrade(1, memory=32) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'], [{'id': 209391}]) + + def test_upgrade_blank(self): + result = self.hardware.upgrade(1) + + self.assertEqual(result, False) + self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) + + def test_upgrade_full(self): + result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", + public_bandwidth=500, test=False) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + class HardwareHelperTests(testing.TestCase): From d7289bb61843e8f3a4aade783fd701fb3f3a921f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:45:26 -0400 Subject: [PATCH 0039/1050] Adding documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..e4d99998c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.upgrade:cli + :prog: hardware upgrade + :show-nested: From 03d032933a31dbe41dfb0d83cbaa043820e98c07 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:10:53 -0400 Subject: [PATCH 0040/1050] Adding documentation. Fix tests. --- tests/CLI/modules/server_tests.py | 1 - tests/managers/hardware_tests.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index efd7eb32b..803d4aab1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -909,4 +909,3 @@ def test_upgrade(self, confirm_mock): result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) - diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e44301c73..4aa3410b8 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -846,7 +846,10 @@ def test_upgrade_full(self): self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] - self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + self.assertIn({'id': 209391}, order_container['prices']) + self.assertIn({'id': 21525}, order_container['prices']) + self.assertIn({'id': 22482}, order_container['prices']) + self.assertIn({'id': 50357}, order_container['prices']) class HardwareHelperTests(testing.TestCase): From 556b1cc1d9290a5fbd67d3ffb97aaf9562d532bf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:41:00 -0400 Subject: [PATCH 0041/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fc083995c..d0436e54f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -20,7 +20,7 @@ LOGGER = logging.getLogger(__name__) # Invalid names are ignored due to long method names and short argument names -# pylint: disable=invalid-name, no-self-use +# pylint: disable=invalid-name, no-self-use, too-many-lines EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', @@ -881,6 +881,8 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded :param value: The value of the parameter to be upgraded + + :returns: int """ option_category = { @@ -897,7 +899,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): product = price.get('item') for category in price.get('categories'): - if not (category.get('categoryCode') == category_code): + if not category.get('categoryCode') == category_code: continue if option == 'disk_controller': From 9741084bbf95d4397815baad71df5838342561b4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:22:31 -0400 Subject: [PATCH 0042/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d0436e54f..5baedd314 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value: The value of the parameter to be upgraded + :param value value: The value of the parameter to be upgraded :returns: int """ From f8a418af42bb1d7c4b399fadc3a02e65e3ff5b20 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:29:36 -0400 Subject: [PATCH 0043/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5baedd314..b303fb5f6 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,6 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value value: The value of the parameter to be upgraded :returns: int """ From 208693481aeaa18daf8887831f1d89f6e6a9379d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:40:55 -0400 Subject: [PATCH 0044/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b303fb5f6..7aef731f8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,9 +881,9 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: int + :returns: A item price id. """ - + price_id = None option_category = { 'memory': 'ram', 'nic_speed': 'port_speed', @@ -903,22 +903,24 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): if option == 'disk_controller': if value == product.get('description'): - return price.get('id') + price_id = price.get('id') elif option == 'nic_speed': if value.isdigit(): if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: split_nic_speed = value.split(" ") if str(product.get('capacity')) == str(split_nic_speed[0]) and \ split_nic_speed[1] in product.get("description"): - return price.get('id') + price_id = price.get('id') elif option == 'bandwidth': if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') + + return price_id def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): From 21a5db744e5e3b239d6fafb1c50b7acdc4301b4a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:42:26 -0400 Subject: [PATCH 0045/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 7aef731f8..ba4494a3e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,7 +881,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: A item price id. + :return: int. """ price_id = None option_category = { From 4443f0b3d796e07f44b55cec7d64b11aff31bc68 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 15 Mar 2021 09:33:37 -0400 Subject: [PATCH 0046/1050] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 731df19ea..0829f782f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,7 +69,10 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) + table.add_row(['preset', utils.lookup(result, 'billingItem', + 'orderItem', + 'preset', + 'keyName') or {}]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 60a6febaa00bf86f370bf76da7f7a2a0454f4b16 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 16 Mar 2021 09:07:09 -0400 Subject: [PATCH 0047/1050] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 0829f782f..01a66cc9f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -72,7 +72,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', - 'keyName') or {}]) + 'keyName') or '-']) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 9c9d4cf1cb62ffa763d3d830477d7d352fa90818 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 17 Mar 2021 17:08:51 -0400 Subject: [PATCH 0048/1050] Add the authorize block and file storage to a hw. --- SoftLayer/CLI/hardware/authorize_storage.py | 25 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 19 ++++++++++++++++ SoftLayer/fixtures/SoftLayer_Hardware.py | 2 ++ SoftLayer/managers/hardware.py | 24 ++++++++++++++++++++ tests/CLI/modules/server_tests.py | 11 +++++++++ tests/managers/hardware_tests.py | 5 +++++ 7 files changed, 87 insertions(+) create mode 100644 SoftLayer/CLI/hardware/authorize_storage.py diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py new file mode 100644 index 000000000..013896883 --- /dev/null +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -0,0 +1,25 @@ +"""Authorize File or Block Storage to a Hardware Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the hardware server") +@environment.pass_env +def cli(env, identifier, username_storage): + """Authorize File or Block Storage to a Hardware Server. + """ + hardware = SoftLayer.HardwareManager(env.client) + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + + if not hardware.authorize_storage(hardware_id, username_storage): + raise exceptions.CLIAbort('Authorize Storage Failed') + env.fout('Successfully Storage Added.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..74a87c10b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), + ('hardware:authorize-storage', 'SoftLayer.CLI.hardware.authorize_storage:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..75e565004 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,22 @@ "resourceTableId": 777777 } ] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index cb902b556..fd6bf9535 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -57,3 +57,5 @@ {'tag': {'name': 'a tag'}} ], } + +allowAccessToNetworkStorageList = True diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ecf1a89c2..34a6c16cf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -786,6 +786,30 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def authorize_storage(self, hardware_id, username_storage): + """Authorize File or Block Storage to a Hardware Server. + + :param int hardware_id: Hardware server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('Hardware', 'allowAccessToNetworkStorageList', + storage_template, id=hardware_id) + + return result + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 5a2db4fc1..3434643a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -903,3 +903,14 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + def test_authorize_hw(self): + result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index c709994b4..e1e3f8cfd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -841,6 +841,11 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_authorize_storage(self): + options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + class HardwareHelperTests(testing.TestCase): From f4b835dfc340f068646db18dbb19fb76008fefa6 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 10:45:01 -0400 Subject: [PATCH 0049/1050] Add documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..490a6608c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.authorize_storage:cli + :prog: hardware authorize-storage + :show-nested: From ac8a4b492080567c6fa938b28379fde8fa4776c5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 14:54:32 -0400 Subject: [PATCH 0050/1050] Fix method documentation. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ba4494a3e..42fd25ba0 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -858,9 +858,9 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): - """Following Method gets all the price ids related to upgrading a VS. + """Following Method gets all the price ids related to upgrading a Hardware Server. - :param int instance_id: Instance id of the VS to be upgraded + :param int instance_id: Instance id of the Hardware Server to be upgraded. :returns: list """ From 1392433cb27e4bdb3d9581ddef08b2f6e23db3c6 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Thu, 18 Mar 2021 18:46:54 -0400 Subject: [PATCH 0051/1050] Update authorize_storage.py Fix docstring. --- SoftLayer/CLI/hardware/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py index 013896883..69b4ba273 100644 --- a/SoftLayer/CLI/hardware/authorize_storage.py +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -15,8 +15,7 @@ help="The storage username to be added to the hardware server") @environment.pass_env def cli(env, identifier, username_storage): - """Authorize File or Block Storage to a Hardware Server. - """ + """Authorize File or Block Storage to a Hardware Server.""" hardware = SoftLayer.HardwareManager(env.client) hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') From 605cab1eb1464919582c0e7fd33a33e8e7e93184 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 12:59:43 -0400 Subject: [PATCH 0052/1050] Fix exception storage username. --- SoftLayer/managers/hardware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a6c16cf..309a019b4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,6 +797,10 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) storage_template = [ { From acde527815b9955a8a3969e8a366b3e06c8ab125 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:02:42 -0400 Subject: [PATCH 0053/1050] Add a unit test to avoid storage username ex. --- tests/CLI/modules/server_tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3434643a2..6c746c9d9 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,6 +910,16 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['hw', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) From 68f2a99f7e92b03392b9da3c05ec46d998184884 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:06:49 -0400 Subject: [PATCH 0054/1050] Add a unit test to manager for storage username. --- tests/managers/hardware_tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e1e3f8cfd..7a008e4fd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,6 +845,13 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.hardware.authorize_storage, + 1234, "#") class HardwareHelperTests(testing.TestCase): From 239452a9c1c24511e5c7c6f175835da3331d1dff Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 13:19:55 -0400 Subject: [PATCH 0055/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 309a019b4..7f0ba1baf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,7 +797,7 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) - + if len(storage_result) == 0: raise SoftLayerError("The Storage with username: %s was not found, please" " enter a valid storage username" % username_storage) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 6c746c9d9..3d3b440f5 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,7 +910,7 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) - + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_authorize_hw_empty(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 7a008e4fd..e39846e31 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,7 +845,7 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) - + def test_authorize_storage_empty(self): mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') mock.return_value = [] From 3853a1f45d7da5480247685b6ae7c2996882951e Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 15:43:34 -0400 Subject: [PATCH 0056/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 + tests/CLI/modules/server_tests.py | 1 + 2 files changed, 2 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8cb4f7acb..e13394355 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -956,6 +956,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): return price_id + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f92fdf9d7..de7ccd95e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -923,6 +923,7 @@ def test_authorize_hw_empty(self, confirm_mock): def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) def test_upgrade_no_options(self, ): result = self.run_command(['hw', 'upgrade', '100']) From fb00f9dd62314fcd6999b9b3e6b7598e6ba5a33c Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 22 Mar 2021 15:45:29 -0400 Subject: [PATCH 0057/1050] Add authorize block, file and portable storage to a vs. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/authorize_storage.py | 41 ++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Account.py | 19 +++++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 6 +++ SoftLayer/managers/vs.py | 42 +++++++++++++++++++ docs/cli/vs.rst | 4 ++ tests/CLI/modules/vs/vs_tests.py | 35 ++++++++++++++++ tests/managers/vs/vs_tests.py | 16 +++++++ 8 files changed, 164 insertions(+) create mode 100644 SoftLayer/CLI/virt/authorize_storage.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..1e1a2db17 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -45,6 +45,7 @@ ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), + ('virtual:authorize-storage', 'SoftLayer.CLI.virt.authorize_storage:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py new file mode 100644 index 000000000..3929e863f --- /dev/null +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -0,0 +1,41 @@ +"""Authorize File, Block and Portable Storage to a Virtual Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the virtual server") +@click.option('--portable-id', type=click.INT, + help="The portable storage id to be added to the virtual server") +@environment.pass_env +def cli(env, identifier, username_storage, portable_id): + """Authorize File, Block and Portable Storage to a Virtual Server. + """ + vs = SoftLayer.VSManager(env.client) + vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") + table.align['name'] = 'r' + table.align['value'] = 'l' + + if username_storage: + if not vs.authorize_storage(vs_id, username_storage): + raise exceptions.CLIAbort('Authorize Volume Failed') + env.fout('Successfully Volume: %s was Added.' % username_storage) + if portable_id: + portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') + portable_result = vs.attach_portable_storage(vs_id, portable_id) + + env.fout('Successfully Portable Storage: %i was Added.' % portable_id) + + table.add_row(['Id', portable_result['id']]) + table.add_row(['createDate', portable_result['createDate']]) + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 3c82b3da3..502c6b295 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1038,3 +1038,22 @@ "statusId": 2 } }] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..76d95dde0 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -875,3 +875,9 @@ migrate = True migrateDedicatedHost = True +allowAccessToNetworkStorageList = True + +attachDiskImage = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b247e8be5..c0eb91b9a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1404,3 +1404,45 @@ def get_hardware_guests(self): object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}} mask = "mask[virtualHost[guests[powerState]]]" return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) + + def authorize_storage(self, vs_id, username_storage): + """Authorize File or Block Storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('SoftLayer_Virtual_Guest', 'allowAccessToNetworkStorageList', + storage_template, id=vs_id) + + return result + + def attach_portable_storage(self, vs_id, portable_id): + """Attach portal storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param int portable_id: Portal storage id. + + :return: SoftLayer_Provisioning_Version1_Transaction. + """ + disk_id = portable_id + result = self.client.call('SoftLayer_Virtual_Guest', 'attachDiskImage', + disk_id, id=vs_id) + + return result diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 811e38c98..6227e0570 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -267,6 +267,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual migrate :show-nested: +.. click:: SoftLayer.CLI.virt.authorize_storage:cli + :prog: virtual authorize-storage + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 7fb23b97b..de3a310b9 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -892,3 +892,38 @@ def test_credentail(self): "username": "user", "password": "pass" }]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_storage_vs_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vs', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_vs_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['vs', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") + + def test_authorize_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) + + def test_authorize_portable_storage_vs(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'attachDiskImage') + mock.return_value = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } + result = self.run_command(['vs', 'authorize-storage', '--portable-id=12345', '1234']) + self.assert_no_fail(result) + + def test_authorize_volume_and_portable_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', + '--portable-id=12345', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index c75e5e3b9..d5202bbe8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1309,3 +1309,19 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) + + def test_authorize_storage(self): + options = self.vs.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.vs.authorize_storage, + 1234, "#") + + def test_authorize_portable_storage(self): + options = self.vs.attach_portable_storage(1234, 1234567) + self.assertEqual(1234567, options['id']) From 75444c4ea9635ac40082c672115a4e55f6a610a1 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:03:46 -0400 Subject: [PATCH 0058/1050] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index 3929e863f..d2080eb52 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -18,8 +18,7 @@ help="The portable storage id to be added to the virtual server") @environment.pass_env def cli(env, identifier, username_storage, portable_id): - """Authorize File, Block and Portable Storage to a Virtual Server. - """ + """Authorize File, Block and Portable Storage to a Virtual Server.""" vs = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") From 4ff8abaae4508f74b19702199ed8f05ac9ac1e5a Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:12:31 -0400 Subject: [PATCH 0059/1050] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index d2080eb52..3228e8800 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -19,19 +19,19 @@ @environment.pass_env def cli(env, identifier, username_storage, portable_id): """Authorize File, Block and Portable Storage to a Virtual Server.""" - vs = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + virtual = SoftLayer.VSManager(env.client) + virtual_id = helpers.resolve_id(virtual.resolve_ids, identifier, 'virtual') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") table.align['name'] = 'r' table.align['value'] = 'l' if username_storage: - if not vs.authorize_storage(vs_id, username_storage): + if not virtual.authorize_storage(virtual_id, username_storage): raise exceptions.CLIAbort('Authorize Volume Failed') env.fout('Successfully Volume: %s was Added.' % username_storage) if portable_id: - portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') - portable_result = vs.attach_portable_storage(vs_id, portable_id) + portable_id = helpers.resolve_id(virtual.resolve_ids, portable_id, 'storage') + portable_result = virtual.attach_portable_storage(virtual_id, portable_id) env.fout('Successfully Portable Storage: %i was Added.' % portable_id) From 9f5abad79d7b5de168cc8a0c67bb439d47ff2384 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Mar 2021 17:16:27 -0500 Subject: [PATCH 0060/1050] #1315 basic IAM authentication support, and slcli login function --- SoftLayer/API.py | 163 +++++++++++++++++++++++++++++++++- SoftLayer/CLI/config/login.py | 96 ++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/__init__.py | 1 + SoftLayer/auth.py | 29 ++++++ SoftLayer/config.py | 18 ++++ SoftLayer/consts.py | 1 + 7 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 430ed2d14..81e667817 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,16 +6,24 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import json +import logging +import requests import warnings from SoftLayer import auth as slauth from SoftLayer import config from SoftLayer import consts +from SoftLayer import exceptions from SoftLayer import transports + +LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT +CONFIG_FILE = consts.CONFIG_FILE + __all__ = [ 'create_client_from_env', 'Client', @@ -80,6 +88,8 @@ def create_client_from_env(username=None, 'Your Company' """ + if config_file is None: + config_file = CONFIG_FILE settings = config.get_client_settings(username=username, api_key=api_key, endpoint_url=endpoint_url, @@ -127,7 +137,7 @@ def create_client_from_env(username=None, settings.get('api_key'), ) - return BaseClient(auth=auth, transport=transport) + return BaseClient(auth=auth, transport=transport, config_file=config_file) def Client(**kwargs): @@ -150,9 +160,35 @@ class BaseClient(object): _prefix = "SoftLayer_" - def __init__(self, auth=None, transport=None): + def __init__(self, auth=None, transport=None, config_file=None): + if config_file is None: + config_file = CONFIG_FILE self.auth = auth - self.transport = transport + self.config_file = config_file + self.settings = config.get_config(self.config_file) + + if transport is None: + url = self.settings['softlayer'].get('endpoint_url') + if url is not None and '/rest' in url: + # If this looks like a rest endpoint, use the rest transport + transport = transports.RestTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + else: + # Default the transport to use XMLRPC + transport = transports.XmlRpcTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -321,6 +357,127 @@ def __repr__(self): def __len__(self): return 0 +class IAMClient(BaseClient): + """IBM ID Client for using IAM authentication + + :param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase + :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) + """ + + + def authenticate_with_password(self, username, password): + """Performs IBM IAM Username/Password Authentication + + :param string username: your IBMid username + :param string password: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'password', + 'password': password, + 'response_type': 'cloud_iam', + 'username': username + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + + config.write_config(self.settings) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens + + def authenticate_with_iam_token(self, a_token, r_token): + """Authenticates to the SL API with an IAM Token + + :param string a_token: Access token + :param string r_token: Refresh Token, to be used if Access token is expired. + """ + self.auth = slauth.BearerAuthentication('', a_token) + user = None + try: + user = self.call('Account', 'getCurrentUser') + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + # self.refresh_iam_token(r_token) + else: + raise ex + return user + + def refresh_iam_token(self, r_token): + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'refresh_token', + 'refresh_token': r_token, + 'response_type': 'cloud_iam' + } + + config = self.settings.get('softlayer') + if config.get('account', False): + data['account'] = account + if config.get('ims_account', False): + data['ims_account'] = ims_account + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + response.raise_for_status() + + LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + config.write_config(self.settings) + return tokens + + + + def call(self, service, method, *args, **kwargs): + """Handles refreshing IAM tokens in case of a HTTP 401 error""" + try: + return super().call(service, method, *args, **kwargs) + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + self.refresh_iam_token(r_token) + return super().call(service, method, *args, **kwargs) + else: + raise ex + + + def __repr__(self): + return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) + class Service(object): """A SoftLayer Service. diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py new file mode 100644 index 000000000..546ce6fdb --- /dev/null +++ b/SoftLayer/CLI/config/login.py @@ -0,0 +1,96 @@ +"""Gets a temporary token for a user""" +# :license: MIT, see LICENSE for more details. +import configparser +import os.path + + +import click +import json +import requests + +import SoftLayer +from SoftLayer import config +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + + email = env.input("Email:") + password = env.getpass("Password:") + + account_id = '' + ims_id = '' + print("ENV CONFIG FILE IS {}".format(env.config_file)) + sl_config = config.get_config(env.config_file) + tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) + print(user) + # tokens = client.authenticate_with_password(email, password) + + # tokens = login(email, password) + # print(tokens) + + + + accounts = get_accounts(tokens['access_token']) + print(accounts) + + # if accounts.get('total_results', 0) == 1: + # selected = accounts['resources'][0] + # account_id = utils.lookup(selected, 'metadata', 'guid') + # ims_id = None + # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): + # if links.get('origin') == "IMS": + # ims_id = links.get('id') + + # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) + # print(tokens) + + # print("Saving Tokens...") + + + for key in sl_config['softlayer']: + print("{} = {} ".format(key, sl_config['softlayer'][key])) + + # sl_config['softlayer']['access_token'] = tokens['access_token'] + # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] + # sl_config['softlayer']['ims_account'] = ims_id + # sl_config['softlayer']['account_id'] = account_id + # config.write_config(sl_config, env.config_file) + # print(sl_config) + + # print("Email: {}, Password: {}".format(email, password)) + + print("Checking for an API key") + + user = client.call('SoftLayer_Account', 'getCurrentUser') + print(user) + + + +def get_accounts(a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + return json.loads(response.text) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..070f32c42 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,6 +70,7 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), + ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), diff --git a/SoftLayer/__init__.py b/SoftLayer/__init__.py index 9e14ea38e..30dd65774 100644 --- a/SoftLayer/__init__.py +++ b/SoftLayer/__init__.py @@ -31,6 +31,7 @@ __copyright__ = 'Copyright 2016 SoftLayer Technologies, Inc.' __all__ = [ # noqa: F405 'BaseClient', + 'IAMClient', 'create_client_from_env', 'Client', 'BasicAuthentication', diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 4046937e6..c2d22168e 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,6 +7,8 @@ """ # pylint: disable=no-self-use +from SoftLayer import config + __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -109,3 +111,30 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + +class BearerAuthentication(AuthenticationBase): + """Bearer Token authentication class. + + :param username str: a user's username, not really needed but all the others use it. + :param api_key str: a user's IAM Token + """ + + def __init__(self, username, token, r_token=None): + """For using IBM IAM authentication + + :param username str: Not really needed, will be set to their current username though for logging + :param token str: the IAM Token + :param r_token str: The refresh Token, optional + """ + self.username = username + self.api_key = token + self.r_token = r_token + + def get_request(self, request): + """Sets token-based auth headers.""" + request.transport_headers['Authorization'] = 'Bearer {}'.format(self.api_key) + request.transport_user = self.username + return request + + def __repr__(self): + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file diff --git a/SoftLayer/config.py b/SoftLayer/config.py index d008893f0..3caebde0f 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -6,9 +6,11 @@ :license: MIT, see LICENSE for more details. """ import configparser +import logging import os import os.path +LOGGER = logging.getLogger(__name__) def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -91,3 +93,19 @@ def get_client_settings(**kwargs): all_settings = settings return all_settings + + +def get_config(config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config = configparser.ConfigParser() + config.read(os.path.expanduser(config_file)) + return config + +def write_config(configuration, config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config_file = os.path.expanduser(config_file) + LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) + with open(config_file, 'w') as file: + configuration.write(file) \ No newline at end of file diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index b10b164d0..71c52be75 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -11,3 +11,4 @@ API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' API_PRIVATE_ENDPOINT_REST = 'https://api.service.softlayer.com/rest/v3.1/' USER_AGENT = "softlayer-python/%s" % VERSION +CONFIG_FILE = "~/.softlayer" From a51e9cf917458dc8a77ddccaeadb320abb75010d Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 23 Mar 2021 15:04:28 -0400 Subject: [PATCH 0061/1050] Refactor hw detail prices. --- SoftLayer/CLI/hardware/detail.py | 10 ++++++---- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..95410fc49 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -78,11 +78,13 @@ def cli(env, identifier, passwords, price): if price: total_price = utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount') or 0 - price_table = formatting.Table(['Item', 'Recurring Price']) - price_table.add_row(['Total', total_price]) + price_table = formatting.Table(['Item', 'CategoryCode', 'Recurring Price']) + price_table.align['Item'] = 'l' - for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row(['Total', '-', total_price]) + + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: + price_table.add_row([item['description'], item['categoryCode'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..dd1dedf68 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -260,7 +260,7 @@ def get_hardware(self, hardware_id, **kwargs): passwords[username,password]],''' 'billingItem[' 'id,nextInvoiceTotalRecurringAmount,' - 'children[nextInvoiceTotalRecurringAmount],' + 'nextInvoiceChildren[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' 'hourlyBillingFlag,' From 1db96f6f26c72223bbe10a7eff60e065cc969db1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Mar 2021 17:33:57 -0400 Subject: [PATCH 0062/1050] add billing and lastTransaction on hardware detail --- SoftLayer/CLI/hardware/detail.py | 2 ++ SoftLayer/managers/hardware.py | 1 + 2 files changed, 3 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..f87f5206d 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,6 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) + table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..9045d9bda 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,6 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' + 'lastTransaction[transactionGroup[name]],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 20630befee1efbaa74f520195fd22235f3f423f8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 24 Mar 2021 17:23:19 -0500 Subject: [PATCH 0063/1050] #1315 moved the IBMID login stuff to config setup instead of its own command --- SoftLayer/API.py | 105 ++++++++++----- SoftLayer/CLI/config/login.py | 96 -------------- SoftLayer/CLI/config/setup.py | 232 +++++++++++++++++++++++++++++----- SoftLayer/CLI/routes.py | 1 - 4 files changed, 275 insertions(+), 159 deletions(-) delete mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 81e667817..2ef237d6c 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -10,7 +10,7 @@ import logging import requests import warnings - +import time from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +18,7 @@ from SoftLayer import exceptions from SoftLayer import transports - +from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -402,29 +402,63 @@ def authenticate_with_password(self, username, password): self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + + return tokens + + def authenticate_with_passcode(self, passcode): + """Performs IBM IAM SSO Authentication + + :param string passcode: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'urn:ibm:params:oauth:grant-type:passcode', + 'passcode': passcode, + 'response_type': 'cloud_iam' + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens - def authenticate_with_iam_token(self, a_token, r_token): + def authenticate_with_iam_token(self, a_token, r_token=None): """Authenticates to the SL API with an IAM Token :param string a_token: Access token :param string r_token: Refresh Token, to be used if Access token is expired. """ - self.auth = slauth.BearerAuthentication('', a_token) - user = None - try: - user = self.call('Account', 'getCurrentUser') - except exceptions.SoftLayerAPIError as ex: - if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - # self.refresh_iam_token(r_token) - else: - raise ex - return user + self.auth = slauth.BearerAuthentication('', a_token, r_token) - def refresh_iam_token(self, r_token): + def refresh_iam_token(self, r_token, account_id=None, ims_account=None): + """Refreshes the IAM Token, will default to values in the config file""" iam_client = requests.Session() headers = { @@ -438,11 +472,15 @@ def refresh_iam_token(self, r_token): 'response_type': 'cloud_iam' } - config = self.settings.get('softlayer') - if config.get('account', False): - data['account'] = account - if config.get('ims_account', False): - data['ims_account'] = ims_account + sl_config = self.settings['softlayer'] + + if account_id is None and sl_config.get('account_id', False): + account_id = sl_config.get('account_id') + if ims_account is None and sl_config.get('ims_account', False): + ims_account = sl_config.get('ims_account') + + data['account'] = account_id + data['ims_account'] = ims_account response = iam_client.request( 'POST', @@ -451,30 +489,37 @@ def refresh_iam_token(self, r_token): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) + response.raise_for_status() - - LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token']) return tokens - - def call(self, service, method, *args, **kwargs): """Handles refreshing IAM tokens in case of a HTTP 401 error""" try: return super().call(service, method, *args, **kwargs) except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - self.refresh_iam_token(r_token) - return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) + # self.refresh_iam_token(r_token) + # return super().call(service, method, *args, **kwargs) + return ex else: raise ex - def __repr__(self): return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py deleted file mode 100644 index 546ce6fdb..000000000 --- a/SoftLayer/CLI/config/login.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Gets a temporary token for a user""" -# :license: MIT, see LICENSE for more details. -import configparser -import os.path - - -import click -import json -import requests - -import SoftLayer -from SoftLayer import config -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.consts import USER_AGENT -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - - email = env.input("Email:") - password = env.getpass("Password:") - - account_id = '' - ims_id = '' - print("ENV CONFIG FILE IS {}".format(env.config_file)) - sl_config = config.get_config(env.config_file) - tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} - client = SoftLayer.API.IAMClient(config_file=env.config_file) - user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) - print(user) - # tokens = client.authenticate_with_password(email, password) - - # tokens = login(email, password) - # print(tokens) - - - - accounts = get_accounts(tokens['access_token']) - print(accounts) - - # if accounts.get('total_results', 0) == 1: - # selected = accounts['resources'][0] - # account_id = utils.lookup(selected, 'metadata', 'guid') - # ims_id = None - # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): - # if links.get('origin') == "IMS": - # ims_id = links.get('id') - - # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) - # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) - # print(tokens) - - # print("Saving Tokens...") - - - for key in sl_config['softlayer']: - print("{} = {} ".format(key, sl_config['softlayer'][key])) - - # sl_config['softlayer']['access_token'] = tokens['access_token'] - # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] - # sl_config['softlayer']['ims_account'] = ims_id - # sl_config['softlayer']['account_id'] = account_id - # config.write_config(sl_config, env.config_file) - # print(sl_config) - - # print("Email: {}, Password: {}".format(email, password)) - - print("Checking for an API key") - - user = client.call('SoftLayer_Account', 'getCurrentUser') - print(user) - - - -def get_accounts(a_token): - """Gets account list from accounts.cloud.ibm.com/v1/accounts""" - iam_client = requests.Session() - - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': USER_AGENT, - 'Accept': 'application/json' - } - headers['Authorization'] = 'Bearer {}'.format(a_token) - response = iam_client.request( - 'GET', - 'https://accounts.cloud.ibm.com/v1/accounts', - headers=headers - ) - - response.raise_for_status() - return json.loads(response.text) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 7f1833a4a..26d7805ee 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,7 +1,10 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. import configparser +import json +import requests import os.path +import webbrowser import click @@ -10,7 +13,11 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer import config as base_config +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils +from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -27,27 +34,68 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur if 'invalid api token' not in ex.faultString.lower(): raise else: - # Try to use a client with username/password - client.authenticate_with_password(username, secret) + if isinstance(client, SoftLayer.API.IAMClient): + client.authenticate_with_iam_token(secret) + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) - user_record = client['Account'].getCurrentUser(mask='id, apiAuthenticationKeys') + user_record = client.call('Account', 'getCurrentUser', mask='id, apiAuthenticationKeys') api_keys = user_record['apiAuthenticationKeys'] if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey(id=user_record['id']) + return client.call('User_Customer', 'addApiAuthenticationKey', id=user_record['id']) return api_keys[0]['authenticationKey'] @click.command() +@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), + help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env -def cli(env): +def cli(env, auth): """Setup the ~/.softlayer file with username and apikey. - Set the username to 'apikey' for cloud.ibm.com accounts. + [Auth Types] + + ibmid: Requires your cloud.ibm.com username and password, and generates a classic infrastructure API key. + + cloud_key: A 32 character API key. Username will be 'apikey' + + classic_key: A 64 character API key used in the Softlayer/Classic Infrastructure systems. + + sso: For users with @ibm.com email addresses. """ + username = None + api_key = None + + timeout = 0 + defaults = config.get_settings_from_client(env.client) + endpoint_url = defaults.get('endpoint_url', 'public') + # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + # Get ths username and API key + if auth == 'ibmid': + print("Logging in with IBMid") + username, api_key = ibmid_login(env) + + elif auth == 'cloud_key': + username = 'apikey' + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) - username, secret, endpoint_url, timeout = get_user_input(env) - new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) - api_key = get_api_key(new_client, username, secret) + elif auth =='sso': + print("Using SSO for login") + username, api_key = sso_login(env) + else: + print("Using a Classic Infrastructure API key") + + username = env.input('Classic Infrastructue Username', default=defaults['username']) + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) + + # Ask for timeout + timeout = env.input('Timeout', default=defaults['timeout'] or 0) path = '~/.softlayer' if env.config_file: @@ -59,8 +107,7 @@ def cli(env): 'endpoint_url': endpoint_url, 'timeout': timeout}))) - if not formatting.confirm('Are you sure you want to write settings ' - 'to "%s"?' % config_path, default=True): + if not formatting.confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): raise exceptions.CLIAbort('Aborted.') # Persist the config file. Read the target config file in before @@ -77,10 +124,7 @@ def cli(env): parsed_config.set('softlayer', 'endpoint_url', endpoint_url) parsed_config.set('softlayer', 'timeout', timeout) - config_fd = os.fdopen(os.open(config_path, - (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), - 0o600), - 'w') + config_fd = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), 0o600), 'w') try: parsed_config.write(config_fd) finally: @@ -89,21 +133,10 @@ def cli(env): env.fout("Configuration Updated Successfully") -def get_user_input(env): - """Ask for username, secret (api_key or password) and endpoint_url.""" +def get_endpoint_url(env, endpoint='public'): + """Gets the Endpoint to use.""" - defaults = config.get_settings_from_client(env.client) - - # Ask for username - username = env.input('Username', default=defaults['username']) - - # Ask for 'secret' which can be api_key or their password - secret = env.getpass('API Key or Password', default=defaults['api_key']) - - # Ask for which endpoint they want to use - endpoint = defaults.get('endpoint_url', 'public') - endpoint_type = env.input( - 'Endpoint (public|private|custom)', default=endpoint) + endpoint_type = env.input('Endpoint (public|private|custom)', default=endpoint) endpoint_type = endpoint_type.lower() if endpoint_type == 'public': @@ -116,7 +149,142 @@ def get_user_input(env): else: endpoint_url = endpoint_type - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) - return username, secret, endpoint_url, timeout + return endpoint_url + +def ibmid_login(env): + """Uses an IBMid and Password to get an access token, and that access token to get an API key""" + email = env.input("Email").strip() + password = env.getpass("Password").strip() + + account_id = '' + ims_id = '' + sl_config = base_config.get_config(env.config_file) + # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_password(email, password) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + + return user.get('username'), api_key + + +def get_accounts(env, a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + + accounts = json.loads(response.text) + selected = None + ims_id = None + if accounts.get('total_results', 0) == 1: + selected = accounts['resources'][0] + else: + env.fout("Select an Account...") + counter = 1 + for selected in accounts.get('resources', []): + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + if ims_id is None: + ims_id = "Unlinked" + env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) + counter = counter + 1 + ims_id = None # Reset ims_id to avoid any mix-match or something. + choice = click.prompt('Enter a number', type=int) + # Test to make sure choice is not out of bounds... + selected = accounts['resources'][choice - 1] + + + account_id = utils.lookup(selected, 'metadata', 'guid') + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + + print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + return {"account_id": account_id, "ims_id": ims_id} + + +def get_sso_url(): + """Gets the URL for using SSO Tokens""" + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + response = iam_client.request( + 'GET', + 'https://iam.cloud.ibm.com/identity/.well-known/openid-configuration', + headers=headers + ) + + response.raise_for_status() + data = json.loads(response.text) + return data.get('passcode_endpoint') + +def sso_login(env): + """Uses a SSO token to get a SL apikey""" + account_id = '' + ims_id = '' + + passcode_url = get_sso_url() + env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) + open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') + if open_browser.lower() == 'y': + webbrowser.open(passcode_url) + passcode = env.input("One-time code") + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_passcode(passcode) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + return user.get('username'), api_key \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 070f32c42..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,7 +70,6 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), - ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), From 7ae7588bd5ee098de67be601f35b3a74439acb1c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Mar 2021 17:22:08 -0500 Subject: [PATCH 0064/1050] Tox fixes --- SoftLayer/API.py | 35 ++++++++--------- SoftLayer/CLI/config/setup.py | 47 ++++++++--------------- SoftLayer/auth.py | 7 ++-- SoftLayer/config.py | 7 +++- tests/CLI/modules/config_tests.py | 64 +++++++++++-------------------- 5 files changed, 64 insertions(+), 96 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 2ef237d6c..5d15681ce 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,11 +6,13 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import time +import warnings + import json import logging import requests -import warnings -import time + from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +20,6 @@ from SoftLayer import exceptions from SoftLayer import transports -from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -188,7 +189,7 @@ def __init__(self, auth=None, transport=None, config_file=None): verify=self.settings['softlayer'].getboolean('verify'), ) - self.transport = transport + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -357,6 +358,7 @@ def __repr__(self): def __len__(self): return 0 + class IAMClient(BaseClient): """IBM ID Client for using IAM authentication @@ -364,8 +366,7 @@ class IAMClient(BaseClient): :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) """ - - def authenticate_with_password(self, username, password): + def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): """Performs IBM IAM Username/Password Authentication :param string username: your IBMid username @@ -394,14 +395,14 @@ def authenticate_with_password(self, username, password): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -434,7 +435,7 @@ def authenticate_with_passcode(self, passcode): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() @@ -443,7 +444,7 @@ def authenticate_with_passcode(self, passcode): self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -489,16 +490,16 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) - + if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) - + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() - + tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -513,9 +514,7 @@ def call(self, service, method, *args, **kwargs): except exceptions.SoftLayerAPIError as ex: if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) - # self.refresh_iam_token(r_token) - # return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) return ex else: raise ex diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 26d7805ee..476ab64b3 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,10 +1,11 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. +import webbrowser + import configparser import json -import requests import os.path -import webbrowser +import requests import click @@ -13,11 +14,9 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import config as base_config from SoftLayer.consts import USER_AGENT from SoftLayer import utils -from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -66,31 +65,26 @@ def cli(env, auth): """ username = None api_key = None - + timeout = 0 defaults = config.get_settings_from_client(env.client) - endpoint_url = defaults.get('endpoint_url', 'public') - # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) # Get ths username and API key if auth == 'ibmid': - print("Logging in with IBMid") username, api_key = ibmid_login(env) - + elif auth == 'cloud_key': username = 'apikey' - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - elif auth =='sso': - print("Using SSO for login") + elif auth == 'sso': username, api_key = sso_login(env) - else: - print("Using a Classic Infrastructure API key") + else: username = env.input('Classic Infrastructue Username', default=defaults['username']) - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) - + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) @@ -148,32 +142,26 @@ def get_endpoint_url(env, endpoint='public'): endpoint_url = env.input('Endpoint URL', default=endpoint) else: endpoint_url = endpoint_type - - return endpoint_url + def ibmid_login(env): """Uses an IBMid and Password to get an access token, and that access token to get an API key""" email = env.input("Email").strip() password = env.getpass("Password").strip() - account_id = '' - ims_id = '' - sl_config = base_config.get_config(env.config_file) - # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} client = SoftLayer.API.IAMClient(config_file=env.config_file) - + # STEP 1: Get the base IAM Token with a username/password tokens = client.authenticate_with_password(email, password) # STEP 2: Figure out which account we want to use account = get_accounts(env, tokens['access_token']) - + # STEP 3: Refresh Token, using a specific account this time. tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) # STEP 4: Get or create the Classic Infrastructure API key - # client.authenticate_with_iam_token(tokens['access_token']) user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") if len(user.get('apiAuthenticationKeys', [])) == 0: @@ -220,12 +208,11 @@ def get_accounts(env, a_token): ims_id = "Unlinked" env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) counter = counter + 1 - ims_id = None # Reset ims_id to avoid any mix-match or something. + ims_id = None # Reset ims_id to avoid any mix-match or something. choice = click.prompt('Enter a number', type=int) # Test to make sure choice is not out of bounds... selected = accounts['resources'][choice - 1] - account_id = utils.lookup(selected, 'metadata', 'guid') links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] for link in links: @@ -256,11 +243,9 @@ def get_sso_url(): data = json.loads(response.text) return data.get('passcode_endpoint') + def sso_login(env): """Uses a SSO token to get a SL apikey""" - account_id = '' - ims_id = '' - passcode_url = get_sso_url() env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') @@ -287,4 +272,4 @@ def sso_login(env): api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) else: api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] - return user.get('username'), api_key \ No newline at end of file + return user.get('username'), api_key diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index c2d22168e..18e0ebe96 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,8 +7,6 @@ """ # pylint: disable=no-self-use -from SoftLayer import config - __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -112,6 +110,7 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + class BearerAuthentication(AuthenticationBase): """Bearer Token authentication class. @@ -121,7 +120,7 @@ class BearerAuthentication(AuthenticationBase): def __init__(self, username, token, r_token=None): """For using IBM IAM authentication - + :param username str: Not really needed, will be set to their current username though for logging :param token str: the IAM Token :param r_token str: The refresh Token, optional @@ -137,4 +136,4 @@ def get_request(self, request): return request def __repr__(self): - return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 3caebde0f..2f48aa221 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -12,6 +12,7 @@ LOGGER = logging.getLogger(__name__) + def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -96,16 +97,18 @@ def get_client_settings(**kwargs): def get_config(config_file=None): + """Returns a parsed config object""" if config_file is None: config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) return config + def write_config(configuration, config_file=None): + """Writes a configuration to config_file""" if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) with open(config_file, 'w') as file: - configuration.write(file) \ No newline at end of file + configuration.write(file) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 33c82520a..fca30af1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -22,9 +22,7 @@ class TestHelpShow(testing.TestCase): def set_up(self): - transport = transports.XmlRpcTransport( - endpoint_url='http://endpoint-url', - ) + transport = transports.XmlRpcTransport(endpoint_url='http://endpoint-url',) self.env.client = SoftLayer.BaseClient( transport=transport, auth=auth.BasicAuthentication('username', 'api-key')) @@ -50,6 +48,7 @@ def set_up(self): # used. transport = testing.MockableTransport(SoftLayer.FixtureTransport()) self.env.client = SoftLayer.BaseClient(transport=transport) + self.config_file = "./test_config_file" @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -62,7 +61,7 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = True getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['public', 'user', 0] result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) @@ -84,55 +83,38 @@ def test_setup_cancel(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = False getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] - - result = self.run_command(['--config=%s' % config_file.name, - 'config', 'setup']) + mocked_input.side_effect = ['public', 'user', 0] + result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_private(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'private', 0] - - username, secret, endpoint_url, timeout = ( - config.get_user_input(self.env)) - - self.assertEqual(username, 'user') - self.assertEqual(secret, 'A' * 64) - self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - self.assertEqual(timeout, 0) - - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_custom(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'custom', 'custom-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - - self.assertEqual(endpoint_url, 'custom-endpoint') - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') def test_github_1074(self, mocked_input, getpass): """Tests to make sure directly using an endpoint works""" - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'test-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - + mocked_input.side_effect = ['test-endpoint'] + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_default(self, mocked_input, getpass): - self.env.getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + def test_get_endpoint(self, mocked_input, getpass): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['private', 'custom', 'test.com', 'public', 'test-endpoint'] + + # private + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - _, _, endpoint_url, _ = config.get_user_input(self.env) + # custom - test.com + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test.com') + # public + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, consts.API_PUBLIC_ENDPOINT) + + # test-endpoint + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test-endpoint') From d7b2d5990f004b7355ca759f34bafa30c56debbd Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:04:20 -0400 Subject: [PATCH 0065/1050] #1450 add slcli order quote-save tests --- tests/CLI/modules/order_tests.py | 5 +++++ tests/managers/ordering_tests.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index f09b5aaea..24495d0ff 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -401,6 +401,11 @@ def test_quote_detail(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getObject', identifier='12345') + def test_quote_save(self): + result = self.run_command(['order', 'quote-save', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier='12345') + def test_quote_list(self): result = self.run_command(['order', 'quote-list']) self.assert_no_fail(result) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f18d801a9..5f88d59d5 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -104,6 +104,13 @@ def test_get_quote_details(self): quote_fixture = quote_service.getObject(id=1234) self.assertEqual(quote, quote_fixture) + def test_save_quote(self): + saved_quote = self.ordering.save_quote(1234) + quote_service = self.ordering.client['Billing_Order_Quote'] + quote_fixture = quote_service.getObject(id=1234) + self.assertEqual(saved_quote, quote_fixture) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier=1234) + def test_verify_quote(self): extras = { 'hardware': [{ From f46990ffe16f16c2be78862e4dd8d8d65cb36b6e Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:08 -0400 Subject: [PATCH 0066/1050] #1450 add slcli order quote-save feature --- SoftLayer/CLI/order/quote_save.py | 33 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Billing_Order_Quote.py | 2 ++ SoftLayer/managers/ordering.py | 7 ++++ 4 files changed, 43 insertions(+) create mode 100644 SoftLayer/CLI/order/quote_save.py diff --git a/SoftLayer/CLI/order/quote_save.py b/SoftLayer/CLI/order/quote_save.py new file mode 100644 index 000000000..64b695ca0 --- /dev/null +++ b/SoftLayer/CLI/order/quote_save.py @@ -0,0 +1,33 @@ +"""Save a quote""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer.utils import clean_time + + +@click.command() +@click.argument('quote') +@environment.pass_env +def cli(env, quote): + """Save a quote""" + + manager = ordering.OrderingManager(env.client) + result = manager.save_quote(quote) + + table = formatting.Table([ + 'Id', 'Name', 'Created', 'Modified', 'Status' + ]) + table.align['Name'] = 'l' + + table.add_row([ + result.get('id'), + result.get('name'), + clean_time(result.get('createDate')), + clean_time(result.get('modifyDate')), + result.get('status'), + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2e73aabf8..631d4b2be 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -227,6 +227,7 @@ ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), + ('order:quote-save', 'SoftLayer.CLI.order.quote_save:cli'), ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py index f1ca8c497..9e7340e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py @@ -102,3 +102,5 @@ ] } } + +saveQuote = getObject diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 36bbb93ef..18513e1e4 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -169,6 +169,13 @@ def get_quote_details(self, quote_id): quote = self.client['Billing_Order_Quote'].getObject(id=quote_id, mask=mask) return quote + def save_quote(self, quote_id): + """Save a quote. + + :param quote_id: ID number of target quote + """ + return self.client['Billing_Order_Quote'].saveQuote(id=quote_id) + def get_order_container(self, quote_id): """Generate an order container from a quote object. From 68292e28474bd57df479cd398c8027f2f6a0f72b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:34 -0400 Subject: [PATCH 0067/1050] #1450 add slcli order quote-save docs --- docs/cli/ordering.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 7405bdb0e..740438a69 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -131,6 +131,10 @@ Quotes :prog: order quote-detail :show-nested: +.. click:: SoftLayer.CLI.order.quote_save:cli + :prog: order quote-save + :show-nested: + .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: From 5c3cd04515a21a2b522d7e776d1cb390155fe2fc Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 10:02:45 -0400 Subject: [PATCH 0068/1050] fix team code review comments --- SoftLayer/CLI/hardware/detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index f87f5206d..c5b1c2b9b 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,7 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['last_transaction', + utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) From 6a8a2d5f982e73d69d9ce2853e94177c8de90d29 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 17:51:41 -0400 Subject: [PATCH 0069/1050] add the Hardware components on "slcli hardware detail" --- SoftLayer/CLI/hardware/detail.py | 12 ++++++++++++ SoftLayer/managers/hardware.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..a1e31e023 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -71,6 +71,8 @@ def cli(env, identifier, passwords, price): bandwidth = hardware.get_bandwidth_allocation(hardware_id) bw_table = _bw_table(bandwidth) table.add_row(['Bandwidth', bw_table]) + system_table = _system_table(result['activeComponents']) + table.add_row(['System_data', system_table]) if result.get('notes'): table.add_row(['notes', result['notes']]) @@ -117,3 +119,13 @@ def _bw_table(bw_data): table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table + + +def _system_table(system_data): + table = formatting.Table(['Type', 'name']) + for system in system_data: + table.add_row([utils.lookup(system, 'hardwareComponentModel', + 'hardwareGenericComponentModel', + 'hardwareComponentType', 'keyName'), + utils.lookup(system, 'hardwareComponentModel', 'longDescription')]) + return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..e7e2d24db 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -239,6 +239,8 @@ def get_hardware(self, hardware_id, **kwargs): 'primaryIpAddress,' 'networkManagementIpAddress,' 'userData,' + 'activeComponents[id,hardwareComponentModel[' + 'hardwareGenericComponentModel[id,hardwareComponentType[keyName]]]],' 'datacenter,' '''networkComponents[id, status, speed, maxSpeed, name, ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress, From 8aa65113dd3b9470bfa524c57e5fe0bedba6ba54 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:57:48 -0500 Subject: [PATCH 0070/1050] fixed code review comments, a few more unit tests --- SoftLayer/API.py | 91 +++++++++++++++++++------------ SoftLayer/CLI/config/setup.py | 4 +- SoftLayer/exceptions.py | 15 +++++ tests/CLI/modules/config_tests.py | 56 +++++++++++++++++++ 4 files changed, 129 insertions(+), 37 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 5d15681ce..a21f9f654 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -175,7 +175,8 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.RestTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + # prevents an exception incase timeout is a float number. + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -184,7 +185,7 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.XmlRpcTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -387,19 +388,25 @@ def authenticate_with_password(self, username, password, security_question_id=No 'username': username } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) - response.raise_for_status() + response.raise_for_status() + tokens = json.loads(response.text) + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -427,19 +434,26 @@ def authenticate_with_passcode(self, passcode): 'response_type': 'cloud_iam' } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) + + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) @@ -483,20 +497,27 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): data['account'] = account_id data['ims_account'] = ims_account - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) - if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 476ab64b3..d5a8ee6f2 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -88,8 +88,8 @@ def cli(env, auth): new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) + # Ask for timeout, convert to float, then to int + timeout = int(float(env.input('Timeout', default=defaults['timeout'] or 0))) path = '~/.softlayer' if env.config_file: diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index b3530aa8c..f444b8389 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,21 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" +class IAMError(SoftLayerError): + """Errors from iam.cloud.ibm.com""" + + def __init__(self, fault_code, fault_string, url=None): + SoftLayerError.__init__(self, fault_string) + self.faultCode = fault_code + self.faultString = fault_string + self.url = url + + def __repr__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + + def __str__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + class SoftLayerAPIError(SoftLayerError): """SoftLayerAPIError is an exception raised during API errors. diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index fca30af1c..7783ac2a2 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +import os import sys import tempfile @@ -50,6 +51,12 @@ def set_up(self): self.env.client = SoftLayer.BaseClient(transport=transport) self.config_file = "./test_config_file" + def tearDown(self): + # Windows doesn't let you write and read from temp files + # So use a real file instead. + if os.path.exists(self.config_file): + os.remove(self.config_file) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -74,6 +81,30 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + @mock.patch('SoftLayer.Client') + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.CLI.environment.Environment.getpass') + @mock.patch('SoftLayer.CLI.environment.Environment.input') + def test_setup_float_timeout(self, mocked_input, getpass, confirm_mock, client): + client.return_value = self.env.client + confirm_mock.return_value = True + getpass.return_value = 'A' * 64 + mocked_input.side_effect = ['public', 'user', 10.0] + + result = self.run_command(['--config=%s' % self.config_file, 'config', 'setup']) + + self.assert_no_fail(result) + self.assertIn('Configuration Updated Successfully', result.output) + + with open(self.config_file, 'r') as config_file: + contents = config_file.read() + self.assertIn('[softlayer]', contents) + self.assertIn('username = user', contents) + self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) + self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + self.assertNotIn('timeout = 10.0\n', contents) + self.assertIn('timeout = 10\n', contents) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -118,3 +149,28 @@ def test_get_endpoint(self, mocked_input, getpass): # test-endpoint endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') + + @mock.patch('SoftLayer.CLI.environment.Environment.input') + @mock.patch('SoftLayer.CLI.config.setup.get_sso_url') + @mock.patch('SoftLayer.CLI.config.setup.get_accounts') + @mock.patch('SoftLayer.API.IAMClient.authenticate_with_passcode') + @mock.patch('SoftLayer.API.IAMClient.refresh_iam_token') + @mock.patch('SoftLayer.API.IAMClient.call') + def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, mocked_input): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['n', '123qweasd'] + get_sso_url.return_value = "https://test.com/" + get_accounts.return_value = {"account_id": 12345, "ims_id": 5555} + passcode.return_value = {"access_token": "aassddffggh", "refresh_token": "qqqqqqq"} + token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} + test_key = "zz112233" + user_object_1 = { + "apiAuthenticationKeys": [{"authenticationKey":test_key}], + "username":"testerson", + "id":99} + api_call.side_effect = [user_object_1] + + user, apikey = config.sso_login(self.env) + self.assertEqual("testerson", user) + self.assertEqual(test_key, apikey) + \ No newline at end of file From 485d9f94a0a41e705c53ff60ae48c26c23b28797 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:59:44 -0500 Subject: [PATCH 0071/1050] Added -a as an option Co-authored-by: ATGE <30413337+ATGE@users.noreply.github.com> --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index d5a8ee6f2..261139a26 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -47,7 +47,7 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur @click.command() -@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), +@click.option('-a', '--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env def cli(env, auth): From 3e34733e76a4d9f0dbb07a7be1327418af313fd9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:24:26 -0500 Subject: [PATCH 0072/1050] tox fixes --- SoftLayer/API.py | 6 +++--- SoftLayer/config.py | 8 ++++++++ SoftLayer/exceptions.py | 1 + tests/CLI/modules/config_tests.py | 7 +++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a21f9f654..a32491ba7 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -405,7 +405,7 @@ def authenticate_with_password(self, username, password, security_question_id=No error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -452,7 +452,7 @@ def authenticate_with_passcode(self, passcode): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -516,7 +516,7 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 2f48aa221..291523d6c 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -102,6 +102,14 @@ def get_config(config_file=None): config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) + # No configuration file found. + if not config.has_section('softlayer'): + config.add_section('softlayer') + config['softlayer']['username'] = '' + config['softlayer']['endpoint_url'] = '' + config['softlayer']['api_key'] = '' + config['softlayer']['timeout'] = 0 + return config diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index f444b8389..d7ce41bc3 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,7 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" + class IAMError(SoftLayerError): """Errors from iam.cloud.ibm.com""" diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 7783ac2a2..5fe917c1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -165,12 +165,11 @@ def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, m token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} test_key = "zz112233" user_object_1 = { - "apiAuthenticationKeys": [{"authenticationKey":test_key}], - "username":"testerson", - "id":99} + "apiAuthenticationKeys": [{"authenticationKey": test_key}], + "username": "testerson", + "id": 99} api_call.side_effect = [user_object_1] user, apikey = config.sso_login(self.env) self.assertEqual("testerson", user) self.assertEqual(test_key, apikey) - \ No newline at end of file From e20ceea73bb670183fe22a7f31c493d5e0d8a374 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:34:44 -0500 Subject: [PATCH 0073/1050] fixing a test --- SoftLayer/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 291523d6c..caa8def10 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -108,7 +108,7 @@ def get_config(config_file=None): config['softlayer']['username'] = '' config['softlayer']['endpoint_url'] = '' config['softlayer']['api_key'] = '' - config['softlayer']['timeout'] = 0 + config['softlayer']['timeout'] = '0' return config From 0f4b627ba74e303d6309cb5eb3df1ce0d211580f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 31 Mar 2021 16:19:23 -0500 Subject: [PATCH 0074/1050] #1395 Forced reserved capacity guests to be monthly as hourly gets ordered as the wrong package --- SoftLayer/CLI/virt/capacity/create_guest.py | 5 ++++- SoftLayer/managers/vs_capacity.py | 3 +++ tests/managers/vs/vs_capacity_tests.py | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create_guest.py b/SoftLayer/CLI/virt/capacity/create_guest.py index 4b8a983a6..07d215205 100644 --- a/SoftLayer/CLI/virt/capacity/create_guest.py +++ b/SoftLayer/CLI/virt/capacity/create_guest.py @@ -34,7 +34,10 @@ help="Test order, will return the order container, but not actually order a server.") @environment.pass_env def cli(env, **args): - """Allows for creating a virtual guest in a reserved capacity.""" + """Allows for creating a virtual guest in a reserved capacity. Only MONTHLY guests are supported at this time. + + If you would like support for hourly reserved capacity guests, please open an issue on the softlayer-python github. + """ create_args = _parse_create_args(env.client, args) create_args['primary_disk'] = args.get('primary_disk') diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 8ce5cf250..727a881b9 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -151,6 +151,9 @@ def create_guest(self, capacity_id, test, guest_object): # Reserved capacity only supports SAN as of 20181008 guest_object['local_disk'] = False + # Reserved capacity only supports monthly ordering via Virtual_Guest::generateOrderTemplate + # Hourly ordering would require building out the order manually. + guest_object['hourly'] = False template = vs_manager.verify_create_instance(**guest_object) template['reservedCapacityId'] = capacity_id if guest_object.get('ipv6'): diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index bb7178055..c6aad9f56 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -116,7 +116,6 @@ def test_create_guest(self): 'disks': (), 'domain': 'test.com', 'hostname': 'A1538172419', - 'hourly': True, 'ipv6': True, 'local_disk': None, 'os_code': 'UBUNTU_LATEST_64', @@ -132,7 +131,7 @@ def test_create_guest(self): 'maxMemory': None, 'hostname': 'A1538172419', 'domain': 'test.com', - 'hourlyBillingFlag': True, + 'hourlyBillingFlag': False, 'supplementalCreateObjectOptions': { 'bootMode': None, 'flavorKeyName': 'B1_1X2X25' From 848d27bb03a279d0758ccab2bb9dbd732b65ac4b Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:33:33 -0400 Subject: [PATCH 0075/1050] Add the option add and upgrade the hw disk. --- SoftLayer/CLI/hardware/upgrade.py | 24 +++++- .../fixtures/SoftLayer_Hardware_Server.py | 39 ++++++++++ SoftLayer/managers/hardware.py | 75 ++++++++++++++++++- tests/CLI/modules/server_tests.py | 35 +++++++++ tests/managers/hardware_tests.py | 31 ++++++++ 5 files changed, 197 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d766000ab..037506df0 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -24,16 +24,22 @@ default=None, type=click.Choice(['Non-RAID', 'RAID'])) @click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--add-disk', nargs=2, multiple=True, type=(int, int), + help="Add a Hard disk in GB to a specific channel, e.g 1000 GB in disk2, it will be " + "--add-disk 1000 2") +@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int), + help="Upgrade a specific disk size in GB, e.g --resize-disk 2000 2") @click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") @environment.pass_env -def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, add_disk, resize_disk, test): """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) - if not any([memory, network, drive_controller, public_bandwidth]): + if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " - " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + " [--memory], [--network], [--drive-controller], [--public-bandwidth]," + "[--add-disk] or [--resize-disk]") hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') if not test: @@ -41,7 +47,17 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, te "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') + disk_list = list() + if add_disk: + for guest_disk in add_disk: + disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if resize_disk: + for guest_disk in resize_disk: + disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, test=test): + public_bandwidth=public_bandwidth, disk=disk_list, test=test): raise exceptions.CLIAbort('Hardware Server Upgrade Failed') env.fout('Successfully Upgraded.') diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index a28d0fc13..0eacab158 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -13,6 +13,10 @@ 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], + 'nextInvoiceChildren': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1, 'categoryCode': 'disk1'}, + {'description': 'test2', 'nextInvoiceTotalRecurringAmount': 2, 'categoryCode': 'disk3'} + ], 'orderItem': { 'order': { 'userRecord': { @@ -336,5 +340,40 @@ "id": 6177, "keyName": "BANDWIDTH_500_GB" } + }, + { + "hourlyRecurringFee": ".023", + "id": 49759, + "recurringFee": "15", + "categories": [ + { + "categoryCode": "disk2", + "id": 6, + "name": "Third Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2", + } + }, + { + "id": 49759, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "disk1", + "id": 5, + "name": "Second Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2" + } } ] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d19a4c423..0ae4929b8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -818,7 +818,7 @@ def authorize_storage(self, hardware_id, username_storage): def upgrade(self, instance_id, memory=None, nic_speed=None, drive_controller=None, - public_bandwidth=None, test=False): + public_bandwidth=None, disk=None, test=False): """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. @@ -826,6 +826,7 @@ def upgrade(self, instance_id, memory=None, :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. :param int public_bandwidth: Public keyName data. + :param list disk: List of disks to add or upgrade Hardware Server. :param bool test: Test option to verify the request. :returns: bool @@ -857,6 +858,10 @@ def upgrade(self, instance_id, memory=None, 'packageId': package_id } + if disk: + prices = self._get_disk_price_list(instance_id, disk) + order['prices'] = prices + for option, value in data.items(): price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) if not price_id: @@ -885,7 +890,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName]]' + 'billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) @@ -924,7 +929,10 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): 'disk_controller': 'disk_controller', 'bandwidth': 'bandwidth' } - category_code = option_category.get(option) + if 'disk' in option: + category_code = option + else: + category_code = option_category.get(option) for price in upgrade_prices: if price.get('categories') is None or price.get('item') is None: @@ -950,12 +958,73 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): elif option == 'bandwidth': if str(product.get('capacity')) == str(value): price_id = price.get('id') + elif 'disk' in option: + if str(product.get('capacity')) == str(value): + price_id = price else: if str(product.get('capacity')) == str(value): price_id = price.get('id') return price_id + def _get_disk_price_list(self, instance_id, disk): + """Get the disks prices to be added or upgraded. + + :param int instance_id: Hardware Server instance id. + :param list disk: List of disks to be added o upgraded to the HW. + + :return list. + """ + prices = [] + disk_exist = False + upgrade_prices = self._get_upgrade_prices(instance_id) + server_response = self.get_instance(instance_id) + for disk_data in disk: + disk_channel = 'disk' + str(disk_data.get('number')) + for item in utils.lookup(server_response, 'billingItem', 'nextInvoiceChildren'): + if disk_channel == item['categoryCode']: + disk_exist = True + break + if disk_exist: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'add_disk') + prices.append(disk_price_detail) + else: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'resize_disk') + prices.append(disk_price_detail) + + return prices + + def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_type): + """Get the disk price detail. + + :param disk_data: List of disks to be added or upgraded. + :param list upgrade_prices: List of item prices. + :param String disk_channel: Disk position. + :param String disk_type: Disk type. + + """ + if disk_data.get('description') == disk_type: + raise SoftLayerError("Unable to add the disk because this already exists." + if "add" in disk_type else "Unable to resize the disk because this does not exists.") + else: + price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, + disk_data.get('capacity')) + if not price_id: + raise SoftLayerError("The item price was not found for %s with 'capacity:' %i" % + (disk_channel, disk_data.get('capacity'))) + + disk_price = { + "id": price_id.get('id'), + "categories": [ + { + "categoryCode": price_id['categories'][0]['categoryCode'], + "id": price_id['categories'][0]['id'] + } + ] + } + + return disk_price + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..f378e9745 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -944,6 +944,41 @@ def test_upgrade_test(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_add_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_resize_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_not_price_found(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_already_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_does_not_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..f7f7c4ea2 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -869,6 +869,13 @@ def test_get_price_id_mismatch_capacity(self): result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) self.assertEqual(92, result) + def test_get_price_id_disk_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'disk1'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'disk1', 1) + self.assertEqual(99, result['id']) + def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) @@ -878,6 +885,30 @@ def test_upgrade(self): order_container = call.args[0] self.assertEqual(order_container['prices'], [{'id': 209391}]) + def test_upgrade_add_disk(self): + disk_list = list() + disks = {'description': 'add_disk', 'capacity': 1000, 'number': 2} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + + def test_upgrade_resize_disk(self): + disk_list = list() + disks = {'description': 'resize_disk', 'capacity': 1000, 'number': 1} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + def test_upgrade_blank(self): result = self.hardware.upgrade(1) From 791c18dd4093d65e5051b2df96de3e68fa21947a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:54:17 -0400 Subject: [PATCH 0076/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0ae4929b8..34a724710 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1004,8 +1004,12 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: - raise SoftLayerError("Unable to add the disk because this already exists." - if "add" in disk_type else "Unable to resize the disk because this does not exists.") + if "add" in disk_type: + disk_type_description = "Unable to add the disk because this already exists." + else: + disk_type_description = "Unable to resize the disk because this does not exists." + + raise SoftLayerError(disk_type_description) else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 6885a47d5513ddceed00fac2f5d71a3d6314ce6d Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 19:53:11 -0400 Subject: [PATCH 0077/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a724710..0c290696f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1005,11 +1005,9 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: if "add" in disk_type: - disk_type_description = "Unable to add the disk because this already exists." + raise SoftLayerError("Unable to add the disk because this already exists.") else: - disk_type_description = "Unable to resize the disk because this does not exists." - - raise SoftLayerError(disk_type_description) + raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 24f7bb9e23c3d7d7153c9f4c1d71831a1c16a2a5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 20:02:23 -0400 Subject: [PATCH 0078/1050] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0c290696f..e161f6ef7 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1006,7 +1006,7 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t if disk_data.get('description') == disk_type: if "add" in disk_type: raise SoftLayerError("Unable to add the disk because this already exists.") - else: + if "resize" in disk_type: raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, From fb2b08ab826b64f34f739981dc173c3930e5131d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 15:23:27 -0500 Subject: [PATCH 0079/1050] #1352 removing the rwhois commands --- SoftLayer/CLI/routes.py | 4 -- SoftLayer/CLI/rwhois/__init__.py | 1 - SoftLayer/CLI/rwhois/edit.py | 55 --------------------- SoftLayer/CLI/rwhois/show.py | 34 ------------- SoftLayer/managers/network.py | 37 +------------- docs/cli/rwhois.rst | 12 ----- tests/CLI/modules/rwhois_tests.py | 81 ------------------------------- tests/managers/network_tests.py | 39 --------------- 8 files changed, 1 insertion(+), 262 deletions(-) delete mode 100644 SoftLayer/CLI/rwhois/__init__.py delete mode 100644 SoftLayer/CLI/rwhois/edit.py delete mode 100644 SoftLayer/CLI/rwhois/show.py delete mode 100644 docs/cli/rwhois.rst delete mode 100644 tests/CLI/modules/rwhois_tests.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 1bb6f5d9c..6307c3a5e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -231,10 +231,6 @@ ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), - ('rwhois', 'SoftLayer.CLI.rwhois'), - ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), - ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), - ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), ('hardware:cancel', 'SoftLayer.CLI.hardware.cancel:cli'), diff --git a/SoftLayer/CLI/rwhois/__init__.py b/SoftLayer/CLI/rwhois/__init__.py deleted file mode 100644 index ef14d6880..000000000 --- a/SoftLayer/CLI/rwhois/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Referral Whois.""" diff --git a/SoftLayer/CLI/rwhois/edit.py b/SoftLayer/CLI/rwhois/edit.py deleted file mode 100644 index 01854bdc9..000000000 --- a/SoftLayer/CLI/rwhois/edit.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Edit the RWhois data on the account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions - - -@click.command() -@click.option('--abuse', help='Set the abuse email address') -@click.option('--address1', help='Update the address 1 field') -@click.option('--address2', help='Update the address 2 field') -@click.option('--city', help='Set the city name') -@click.option('--company', help='Set the company name') -@click.option('--country', help='Set the two-letter country code') -@click.option('--firstname', help='Update the first name field') -@click.option('--lastname', help='Update the last name field') -@click.option('--postal', help='Set the postal code field') -@click.option('--public/--private', - default=None, - help='Flags the address as a public or private residence.') -@click.option('--state', help='Set the two-letter state code') -@environment.pass_env -def cli(env, abuse, address1, address2, city, company, country, firstname, - lastname, postal, public, state): - """Edit the RWhois data on the account.""" - mgr = SoftLayer.NetworkManager(env.client) - - update = { - 'abuse_email': abuse, - 'address1': address1, - 'address2': address2, - 'company_name': company, - 'city': city, - 'country': country, - 'first_name': firstname, - 'last_name': lastname, - 'postal_code': postal, - 'state': state, - 'private_residence': public, - } - - if public is True: - update['private_residence'] = False - elif public is False: - update['private_residence'] = True - - check = [x for x in update.values() if x is not None] - if not check: - raise exceptions.CLIAbort( - "You must specify at least one field to update.") - - mgr.edit_rwhois(**update) diff --git a/SoftLayer/CLI/rwhois/show.py b/SoftLayer/CLI/rwhois/show.py deleted file mode 100644 index 9e862b0f3..000000000 --- a/SoftLayer/CLI/rwhois/show.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Display the RWhois information for your account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """Display the RWhois information for your account.""" - - mgr = SoftLayer.NetworkManager(env.client) - result = mgr.get_rwhois() - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) - table.add_row(['Company', result['companyName']]) - table.add_row(['Abuse Email', result['abuseEmail']]) - table.add_row(['Address 1', result['address1']]) - if result.get('address2'): - table.add_row(['Address 2', result['address2']]) - table.add_row(['City', result['city']]) - table.add_row(['State', result.get('state', '-')]) - table.add_row(['Postal Code', result.get('postalCode', '-')]) - table.add_row(['Country', result['country']]) - table.add_row(['Private Residence', result['privateResidenceFlag']]) - - env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 11ed9733e..d609de5d5 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -56,7 +56,7 @@ class NetworkManager(object): - """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois + """Manage SoftLayer network objects: VLANs, subnets and IPs See product information here: https://www.ibm.com/cloud/network @@ -311,34 +311,6 @@ def detach_securitygroup_components(self, group_id, component_ids): return self.security_group.detachNetworkComponents(component_ids, id=group_id) - def edit_rwhois(self, abuse_email=None, address1=None, address2=None, - city=None, company_name=None, country=None, - first_name=None, last_name=None, postal_code=None, - private_residence=None, state=None): - """Edit rwhois record.""" - update = {} - for key, value in [('abuseEmail', abuse_email), - ('address1', address1), - ('address2', address2), - ('city', city), - ('companyName', company_name), - ('country', country), - ('firstName', first_name), - ('lastName', last_name), - ('privateResidenceFlag', private_residence), - ('state', state), - ('postalCode', postal_code)]: - if value is not None: - update[key] = value - - # If there's anything to update, update it - if update: - rwhois = self.get_rwhois() - return self.client['Network_Subnet_Rwhois_Data'].editObject( - update, id=rwhois['id']) - - return True - def edit_securitygroup(self, group_id, name=None, description=None): """Edit security group details. @@ -408,13 +380,6 @@ def ip_lookup(self, ip_address): obj = self.client['Network_Subnet_IpAddress'] return obj.getByIpAddress(ip_address, mask='hardware, virtualGuest') - def get_rwhois(self): - """Returns the RWhois information about the current account. - - :returns: A dictionary containing the account's RWhois information. - """ - return self.account.getRwhoisData() - def get_securitygroup(self, group_id, **kwargs): """Returns the information about the given security group. diff --git a/docs/cli/rwhois.rst b/docs/cli/rwhois.rst deleted file mode 100644 index 10d2004c9..000000000 --- a/docs/cli/rwhois.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. _cli_rwhois: - -Reverse Whois Commands -====================== - -.. click:: SoftLayer.CLI.rwhois.edit:cli - :prog: rwhois edit - :show-nested: - -.. click:: SoftLayer.CLI.rwhois.show:cli - :prog: rwhois show - :show-nested: diff --git a/tests/CLI/modules/rwhois_tests.py b/tests/CLI/modules/rwhois_tests.py deleted file mode 100644 index 6409bf884..000000000 --- a/tests/CLI/modules/rwhois_tests.py +++ /dev/null @@ -1,81 +0,0 @@ -""" - SoftLayer.tests.CLI.modules.rwhois_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -from SoftLayer.CLI import exceptions -from SoftLayer import testing - -import json - - -class RWhoisTests(testing.TestCase): - def test_edit_nothing(self): - - result = self.run_command(['rwhois', 'edit']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - - def test_edit(self): - - result = self.run_command(['rwhois', 'edit', - '--abuse=abuse@site.com', - '--address1=address line 1', - '--address2=address line 2', - '--company=Company, Inc', - '--city=Dallas', - '--country=United States', - '--firstname=John', - '--lastname=Smith', - '--postal=12345', - '--state=TX', - '--state=TX', - '--private']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'city': 'Dallas', - 'firstName': 'John', - 'companyName': 'Company, Inc', - 'address1': 'address line 1', - 'address2': 'address line 2', - 'lastName': 'Smith', - 'abuseEmail': 'abuse@site.com', - 'state': 'TX', - 'country': 'United States', - 'postalCode': '12345', - 'privateResidenceFlag': True},), - identifier='id') - - def test_edit_public(self): - result = self.run_command(['rwhois', 'edit', '--public']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'privateResidenceFlag': False},), - identifier='id') - - def test_show(self): - self.maxDiff = 100000 - result = self.run_command(['rwhois', 'show']) - - expected = {'Abuse Email': 'abuseEmail', - 'Address 1': 'address1', - 'Address 2': 'address2', - 'City': 'city', - 'Company': 'companyName', - 'Country': 'country', - 'Name': 'firstName lastName', - 'Postal Code': 'postalCode', - 'State': '-', - 'Private Residence': True} - self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), expected) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 361ea1a61..80d054f9d 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -222,39 +222,6 @@ def test_detach_securitygroup_components(self): 'detachNetworkComponents', identifier=100, args=([500, 600],)) - def test_edit_rwhois(self): - result = self.network.edit_rwhois( - abuse_email='abuse@test.foo', - address1='123 Test Street', - address2='Apt. #31', - city='Anywhere', - company_name='TestLayer', - country='US', - first_name='Bob', - last_name='Bobinson', - postal_code='9ba62', - private_residence=False, - state='TX') - - self.assertEqual(result, True) - expected = { - 'abuseEmail': 'abuse@test.foo', - 'address1': '123 Test Street', - 'address2': 'Apt. #31', - 'city': 'Anywhere', - 'companyName': 'TestLayer', - 'country': 'US', - 'firstName': 'Bob', - 'lastName': 'Bobinson', - 'postalCode': '9ba62', - 'privateResidenceFlag': False, - 'state': 'TX', - } - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - identifier='id', - args=(expected,)) - def test_edit_securitygroup(self): result = self.network.edit_securitygroup(100, name='foobar') @@ -290,12 +257,6 @@ def test_edit_securitygroup_rule_unset(self): 'portRangeMin': -1, 'portRangeMax': -1, 'ethertype': '', 'remoteIp': ''}],)) - def test_get_rwhois(self): - result = self.network.get_rwhois() - - self.assertEqual(result, fixtures.SoftLayer_Account.getRwhoisData) - self.assert_called_with('SoftLayer_Account', 'getRwhoisData') - def test_get_securitygroup(self): result = self.network.get_securitygroup(100) From 262507e07c861ee249f959d2301cdc1d899789a1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 16:07:00 -0500 Subject: [PATCH 0080/1050] #1457 added contributing guide --- CONTRIBUTING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1eed6d308..9182f802b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,14 +3,22 @@ We are happy to accept contributions to softlayer-python. Please follow the guidelines below. -* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/dev/cla-individual.md). +## Procedural -* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). - -* Fork the repo, make your changes, and open a pull request. +1. All code changes require a corresponding issue. [Open an issue here](https://github.com/softlayer/softlayer-python/issues). +2. Fork the [softlayer-python](https://github.com/softlayer/softlayer-python) repository. +3. Make any changes required, commit messages should reference the issue number (include #1234 if the message if your issue is number 1234 for example). +4. Make a pull request from your fork/branch to origin/master +5. Requires 1 approval for merging * Additional infomration can be found in our [contribution guide](http://softlayer-python.readthedocs.org/en/latest/dev/index.html) +## Legal + +* See our [Contributor License Agreement](./docs/dev/cla-individual.md). Opening a pull request is acceptance of this agreement. + +* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). + ## Code style @@ -101,4 +109,48 @@ order_args = getattr(order_call[0], 'args')[0] # Test our specific argument value self.assertEqual(123, order_args['hostId']) -``` \ No newline at end of file +``` + + +## Project Management + +### Issues + +* ~~Title~~: Should contain quick highlight of the issue is about +* ~~Body~~: All the technical information goes here +* ~~Assignee~~: Should be the person who is actively working on an issue. +* ~~Label~~: All issues should have at least 1 Label. +* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. + +### Pull Requests + +* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* ~~Reviewers~~: 1 Reviewer is required +* ~~Assignee~~: Should be the person who opened the pull request +* ~~Labels~~: Should match the issue +* ~~Projects~~: Should match the issue +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. + +### Code Reviews +All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. + +#### Things to look for while doing a review + +As a reviewer, these are some guidelines when doing a review, but not hard rules. + +* Code Style: Generally `tox -e analysis` will pick up most style violations, but anything that is wildly different from the normal code patters in this project should be changed to match, unless there is a strong reason to not do so. +* API Calls: Close attention should be made to any new API calls, to make sure they will work as expected, and errors are handled if needed. +* DocBlock comments: CLI and manager methods need to be documented well enough for users to easily understand what they do. +* Easy to read code: Code should generally be easy enough to understand at a glance. Confusing code is a sign that it should either be better documented, or refactored a bit to be clearer in design. + + +### Testing + +When doing testing of a code change, indicate this with a comment on the pull request like + +:heavy_check: `slcli vs list --new-feature` +:x: `slcli vs list --broken-feature` From 26d9162db780889617e0f6071b1b1bc1db0365d9 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 09:01:27 -0400 Subject: [PATCH 0081/1050] Add an --orderBy parameters to call-api --- SoftLayer/CLI/call_api.py | 8 +++++++- SoftLayer/utils.py | 16 ++++++++++++++++ tests/CLI/modules/call_api_tests.py | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cbce4eccb..cf0a2b871 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,6 +112,9 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") +@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" + "E.G --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -119,7 +122,7 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument "Remember to use double quotes (\") for variable names. Can NOT be used with --filter. " "Dont use whitespace outside of strings, or the slcli might have trouble parsing it.") @environment.pass_env -def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, +def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, orderby=None, output_python=False, json_filter=None): """Call arbitrary API endpoints with the given SERVICE and METHOD. @@ -147,6 +150,9 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") object_filter = _build_filters(_filters) + if orderby: + _filters = utils.build_filter_orderby(orderby) + object_filter.update(_filters) if json_filter: object_filter.update(json_filter) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..3900bb9dd 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -200,6 +200,22 @@ def event_log_filter_less_than_date(date, utc): } +def build_filter_orderby(orderby): + _filters = {} + aux = list(reversed(str(orderby).split('.'))) + for split in aux: + _aux_filter = {} + if str(split).__contains__('='): + _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + _filters = _aux_filter + elif split == list(aux)[0]: + _aux_filter[split] = query_filter_orderby('DESC') + else: + _aux_filter[split] = _filters + _filters = _aux_filter + return _filters + + class IdentifierMixin(object): """Mixin used to resolve ids from other names of objects. diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index 8d3f19ab2..b98998f29 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -298,3 +298,17 @@ def test_json_filter(self): result = self.run_command(['call-api', 'Account', 'getObject', '--json-filter={"test":"something"}']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject', filter={"test": "something"}) + + def test_call_api_orderBy(self): + result = self.run_command(['call-api', 'Account', 'getVirtualGuests', + '--orderBy', 'virtualGuests.id=DESC']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', + 'getVirtualGuests', + filter={ + 'virtualGuests': + {'id': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}}}) From c232336bcf8e46f4181e3e5e85d62806b9f69453 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 11:16:46 -0400 Subject: [PATCH 0082/1050] Add method comment --- SoftLayer/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 3900bb9dd..f5cdce405 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,6 +201,12 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): + """ + Builds filters using the filter options passed into the CLI. + + only support fot create filter option orderBy, default value is DESC + + """ _filters = {} aux = list(reversed(str(orderby).split('.'))) for split in aux: From 447b3aa5303be9005706b667ba37488b61e7ff0c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 12:02:00 -0400 Subject: [PATCH 0083/1050] Add method comment --- SoftLayer/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index f5cdce405..ac2e34099 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,11 +201,9 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): - """ - Builds filters using the filter options passed into the CLI. - - only support fot create filter option orderBy, default value is DESC + """Builds filters using the filter options passed into the CLI. + Only support fot create filter option orderBy, default value is DESC. """ _filters = {} aux = list(reversed(str(orderby).split('.'))) From 19f28989606c30c404299659221066163111c591 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 18:23:04 -0400 Subject: [PATCH 0084/1050] fix Christopher code review comments --- SoftLayer/CLI/hardware/detail.py | 7 +++++-- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index c5b1c2b9b..512850dba 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,8 +61,11 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['last_transaction', - utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) + + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9045d9bda..ea2fb1a63 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,7 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' - 'lastTransaction[transactionGroup[name]],' + 'lastTransaction[transactionGroup],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 10bfe14bb64f3e1ce189afc781387b0adfc3cced Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 14:34:44 -0500 Subject: [PATCH 0085/1050] #1436 added checking for a special character sequence for when windows users use shift+ins to paste into a password field --- SoftLayer/CLI/environment.py | 17 ++++++++++++++++- tests/CLI/environment_tests.py | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c73bc6385..b1670f949 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -67,7 +67,22 @@ def input(self, prompt, default=None, show_default=True): def getpass(self, prompt, default=None): """Provide a password prompt.""" - return click.prompt(prompt, hide_input=True, default=default) + password = click.prompt(prompt, hide_input=True, default=default) + + # https://github.com/softlayer/softlayer-python/issues/1436 + # click.prompt uses python's getpass() in the background + # https://github.com/python/cpython/blob/3.9/Lib/getpass.py#L97 + # In windows, shift+insert actually inputs the below 2 characters + # If we detect those 2 characters, need to manually read from the clipbaord instead + # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard + if password == 'àR': + # tkinter is a built in python gui, but it has clipboard reading functions. + from tkinter import Tk + tk_manager = Tk() + password = tk_manager.clipboard_get() + # keep the window from showing + tk_manager.withdraw() + return password # Command loading methods def list_commands(self, *path): diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..f000819a6 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -55,6 +55,15 @@ def test_getpass(self, prompt_mock): prompt_mock.assert_called_with('input', default=None, hide_input=True) self.assertEqual(prompt_mock(), r) + @mock.patch('click.prompt') + @mock.patch('tkinter.Tk.clipboard_get') + def test_getpass_issues1436(self, tk, prompt_mock): + tk.return_value = 'test_from_clipboard' + prompt_mock.return_value = 'àR' + r = self.env.getpass('input') + prompt_mock.assert_called_with('input', default=None, hide_input=True) + self.assertEqual('test_from_clipboard', r) + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From 391bf8ccd7e2537d3d213c449d928f83a1882070 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:13:48 -0500 Subject: [PATCH 0086/1050] fixing a unit test --- tests/CLI/environment_tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f000819a6..b6d275941 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -7,6 +7,7 @@ import click import mock +# from unittest.mock import MagicMock from SoftLayer.CLI import environment from SoftLayer import testing @@ -56,13 +57,13 @@ def test_getpass(self, prompt_mock): self.assertEqual(prompt_mock(), r) @mock.patch('click.prompt') - @mock.patch('tkinter.Tk.clipboard_get') + @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): - tk.return_value = 'test_from_clipboard' prompt_mock.return_value = 'àR' r = self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) - self.assertEqual('test_from_clipboard', r) + tk.assert_called_with() + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} From 07df909a4321b77211db521158ba11073852e6a3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:32:58 -0500 Subject: [PATCH 0087/1050] tox fixes --- SoftLayer/CLI/environment.py | 1 + tests/CLI/environment_tests.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index b1670f949..e16c5cde9 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -77,6 +77,7 @@ def getpass(self, prompt, default=None): # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard if password == 'àR': # tkinter is a built in python gui, but it has clipboard reading functions. + # pylint: disable=import-outside-toplevel from tkinter import Tk tk_manager = Tk() password = tk_manager.clipboard_get() diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index b6d275941..a7a91d0f2 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -60,11 +60,10 @@ def test_getpass(self, prompt_mock): @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): prompt_mock.return_value = 'àR' - r = self.env.getpass('input') + self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) tk.assert_called_with() - def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From c8bb4ff7c8157ee768c5cb4ac4c5975a1abbc530 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 9 Apr 2021 15:19:50 -0500 Subject: [PATCH 0088/1050] removed reference to rwhois in the documentation --- docs/cli.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index dc82da29f..a659b145c 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -92,7 +92,6 @@ To discover the available commands, simply type `slcli`. object-storage Object Storage. order View and order from the catalog. report Reports. - rwhois Referral Whois. securitygroup Network security groups. setup Edit configuration. shell Enters a shell for slcli. From 9c4f3ba229d29060a2960c6f224ea801e94207d9 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 9 Apr 2021 19:32:17 -0400 Subject: [PATCH 0089/1050] Fix slcli hw upgrade disk to support with rest. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e161f6ef7..2b6eb7f8f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -896,7 +896,7 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) - def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + def _get_upgrade_prices(self, instance_id): """Following Method gets all the price ids related to upgrading a Hardware Server. :param int instance_id: Instance id of the Hardware Server to be upgraded. @@ -910,7 +910,7 @@ def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): 'item[keyName,description,capacity,units]' ] mask = "mask[%s]" % ','.join(mask) - return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + return self.hardware.getUpgradeItemPrices(id=instance_id, mask=mask) @staticmethod def _get_prices_for_upgrade_option(upgrade_prices, option, value): From f39538a3066229a3667f1e1afb705320d1ab9177 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 13 Apr 2021 15:49:31 -0400 Subject: [PATCH 0090/1050] add Billing and lastTransaction on slcli virtual detail --- SoftLayer/CLI/virt/detail.py | 5 +++++ SoftLayer/managers/vs.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 01a66cc9f..ac9453ddd 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,11 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index c0eb91b9a..77410ff4a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -227,7 +227,7 @@ def get_instance(self, instance_id, **kwargs): 'maxMemory,' 'datacenter,' 'activeTransaction[id, transactionStatus[friendlyName,name]],' - 'lastTransaction[transactionStatus],' + 'lastTransaction[transactionStatus,modifyDate,transactionGroup[name]],' 'lastOperatingSystemReload.id,' 'blockDevices,' 'blockDeviceTemplateGroup[id, name, globalIdentifier],' From 9e27d841e0b8512229839c45c29b19bb69545b5c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 15:34:13 -0500 Subject: [PATCH 0091/1050] #1462 Added automation to publish to test-pypi in preperation for fully automating the build process --- .github/workflows/test_pypi_release.yml | 37 ++++++++++++++ README.rst | 7 ++- RELEASE.md | 68 ++++++++++++++++++++++--- 3 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test_pypi_release.yml diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml new file mode 100644 index 000000000..5e7c6b683 --- /dev/null +++ b/.github/workflows/test_pypi_release.yml @@ -0,0 +1,37 @@ +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Publish 📦 to TestPyPI + +on: + push: + branches: [ master ] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/README.rst b/README.rst index 75e5d6f54..2ae928347 100644 --- a/README.rst +++ b/README.rst @@ -28,8 +28,7 @@ SoftLayer products and services. Documentation ------------- -Documentation for the Python client is available at -http://softlayer.github.io/softlayer-python/. +Documentation for the Python client is available at `Read the Docs `_ . Additional API documentation can be found on the SoftLayer Development Network: @@ -38,7 +37,7 @@ Additional API documentation can be found on the SoftLayer Development Network: * `Object mask information and examples `_ * `Code Examples - `_ + `_ Installation ------------ @@ -82,7 +81,7 @@ Issues with the Softlayer API itself should be addressed by opening a ticket. Examples -------- -A curated list of examples on how to use this library can be found at `softlayer.github.io `_ +A curated list of examples on how to use this library can be found at `SLDN `_ Debugging --------- diff --git a/RELEASE.md b/RELEASE.md index eb1cb6d47..962ee1663 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,18 +1,74 @@ -# Release steps -* Update version constants (find them by running `git grep [VERSION_NUMBER]`) -* Create changelog entry (edit CHANGELOG.md with a one-liner for each closed issue going in the release) -* Commit and push changes to master with the message: "Version Bump to v[VERSION_NUMBER]" -* Make sure your `upstream` repo is set + +# Versions + +This project follows the Major.Minor.Revision versioning system. Fixes, and minor additions would increment Revision. Large changes and additions would increment Minor, and anything that would be a "Breaking" change, or redesign would be an increment of Major. + +# Changelog + +When doing a release, the Changelog format should be as follows: + +```markdown + +## [Version] - YYYY-MM-DD +https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + +#### New Command +- `slcli new command` #issueNumber + +#### Improvements +- List out improvements #issueNumber +- Something else that changed #issueNumber + +#### Deprecated +- List something that got removed #issueNumber + +``` + +# Normal Release steps + +A "release" of the softlayer-python project is the current state of the `master` branch. Any changes in the master branch should be considered releaseable. + + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Make sure the tests for the build all pass +4. [Draft a new release](https://github.com/softlayer/softlayer-python/releases/new) + - Version should start with `v` followed by Major.Minor.Revision: `vM.m.r` + - Title should be `M.m.r` + - Description should be the release notes + - Target should be the `master` branch +5. The github automation should take care of publishing the release to [PyPi](https://pypi.org/project/SoftLayer/). This may take a few minutes to update. + +# Manual Release steps + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Commit your changes to `master`, and make sure `softlayer/softlayer-python` repo is updated to reflect that +4. Make sure your `upstream` repo is set + ``` git remote -v upstream git@github.com:softlayer/softlayer-python.git (fetch) upstream git@github.com:softlayer/softlayer-python.git (push) ``` -* Push tag and PyPi `python fabfile.py 5.7.2`. Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: + +5. Create and publish the package + - Make sure you have `twine` installed, this is what uploads the pacakge to PyPi. + - Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: ``` [server-login] username:YOUR_USERNAME password:YOUR_PASSWORD ``` + + - Run `python fabfile.py 5.7.2`. Where `5.7.2` is the `M.m.r` version number. Don't use the `v` here in the version number. + + +*NOTE* PyPi doesn't let you reupload a version, if you upload a bad package for some reason, you have to create a new version. + From 53fc65625e25aa4206aa0712ce2811448836557c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:39:38 -0500 Subject: [PATCH 0092/1050] Added a utility to merge objectFilters, #1459 1461 --- SoftLayer/utils.py | 16 ++++++++++++++++ tests/basic_tests.py | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..bd7f33c91 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import collections import datetime import re import time @@ -57,6 +58,21 @@ def to_dict(self): for key, val in self.items()} +def dict_merge(dct1, dct2): + """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + + :param dct1: dict onto which the merge is executed + :param dct2: dct merged into dct + :return: None + """ + + for k, v in dct2.items(): + if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): + dict_merge(dct1[k], dct2[k]) + else: + dct1[k] = dct2[k] + + def query_filter(query): """Translate a query-style string to a 'filter'. diff --git a/tests/basic_tests.py b/tests/basic_tests.py index b430a3d5e..f4dbe6085 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -79,6 +79,16 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.dst()) self.assertEqual(datetime.timedelta(0), time.utcoffset()) + def test_dict_merge(self): + filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} + filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + SoftLayer.utils.dict_merge(filter1, filter2) + + self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') + self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + + + class TestNestedDict(testing.TestCase): From ffce9b3020437e99d46d850165c2d5a713dd7ad0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:48:06 -0500 Subject: [PATCH 0093/1050] changed dict_merge to return a merged dictionary --- SoftLayer/utils.py | 14 ++++++++------ tests/basic_tests.py | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index bd7f33c91..c0851ec4a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -59,18 +59,20 @@ def to_dict(self): def dict_merge(dct1, dct2): - """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + """Recursively merges dct2 and dct1, ideal for merging objectFilter together. - :param dct1: dict onto which the merge is executed - :param dct2: dct merged into dct - :return: None + :param dct1: A dictionary + :param dct2: A dictionary + :return: dct1 + dct2 """ + dct = dct1.copy() for k, v in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): - dict_merge(dct1[k], dct2[k]) + dct[k] = dict_merge(dct1[k], dct2[k]) else: - dct1[k] = dct2[k] + dct[k] = dct2[k] + return dct def query_filter(query): diff --git a/tests/basic_tests.py b/tests/basic_tests.py index f4dbe6085..dbbcbef0a 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -82,10 +82,11 @@ def test_timezone(self): def test_dict_merge(self): filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} - SoftLayer.utils.dict_merge(filter1, filter2) + result = SoftLayer.utils.dict_merge(filter1, filter2) - self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') - self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') + self.assertNotIn('id', filter1['virtualGuests']) + self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') From 554cbbd33fbc3c40c2751c8f2182e92c5d24a3ff Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 18:42:37 -0500 Subject: [PATCH 0094/1050] tox fixes --- SoftLayer/utils.py | 2 +- tests/basic_tests.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index c0851ec4a..83bd79eae 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -67,7 +67,7 @@ def dict_merge(dct1, dct2): """ dct = dct1.copy() - for k, v in dct2.items(): + for k, _ in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): dct[k] = dict_merge(dct1[k], dct2[k]) else: diff --git a/tests/basic_tests.py b/tests/basic_tests.py index dbbcbef0a..59bd76d86 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -80,8 +80,8 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.utcoffset()) def test_dict_merge(self): - filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} - filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + filter1 = {"virtualGuests": {"hostname": {"operation": "etst"}}} + filter2 = {"virtualGuests": {"id": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}}} result = SoftLayer.utils.dict_merge(filter1, filter2) self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') @@ -89,8 +89,6 @@ def test_dict_merge(self): self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') - - class TestNestedDict(testing.TestCase): def test_basic(self): From 5caa6ce09f477f6f4215702bc32c945fbb968a7e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 14 Apr 2021 14:23:37 -0500 Subject: [PATCH 0095/1050] Updating author_email to SLDN distro list --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6d51d38ce..a09cca0a4 100644 --- a/setup.py +++ b/setup.py @@ -19,8 +19,8 @@ version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, - author='SoftLayer Technologies, Inc.', - author_email='sldn@softlayer.com', + author='SoftLayer, Inc., an IBM Company', + author_email='SLDNDeveloperRelations@wwpdl.vnet.ibm.com', packages=find_packages(exclude=['tests']), license='MIT', zip_safe=False, From 9a5f20ac0056cdbaa571395a05c1ad2ba0c5fb2c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 15 Apr 2021 14:47:16 -0500 Subject: [PATCH 0096/1050] fixed some style issues --- CONTRIBUTING.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9182f802b..2ec9136a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,24 +116,24 @@ self.assertEqual(123, order_args['hostId']) ### Issues -* ~~Title~~: Should contain quick highlight of the issue is about -* ~~Body~~: All the technical information goes here -* ~~Assignee~~: Should be the person who is actively working on an issue. -* ~~Label~~: All issues should have at least 1 Label. -* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. +* _Title_: Should contain quick highlight of the issue is about +* _Body_: All the technical information goes here +* _Assignee_: Should be the person who is actively working on an issue. +* _Label_: All issues should have at least 1 Label. +* _Projects_: Should be added to the quarerly Softlayer project when being worked on +* _Milestones_: Not really used, can be left blank +* _Linked Pull Request_: Should be linked to the relavent pull request when it is opened. ### Pull Requests -* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request -* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. -* ~~Reviewers~~: 1 Reviewer is required -* ~~Assignee~~: Should be the person who opened the pull request -* ~~Labels~~: Should match the issue -* ~~Projects~~: Should match the issue -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. +* _Title_: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* _Body_: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* _Reviewers_: 1 Reviewer is required +* _Assignee_: Should be the person who opened the pull request +* _Labels_: Should match the issue +* _Projects_: Should match the issue +* _Milestones_: Not really used, can be left blank +* _Linked issues_: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. ### Code Reviews All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. From 1af447c63b33047591516c9ec89347754d6257e0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 15:51:34 -0400 Subject: [PATCH 0097/1050] add the firewall information on slcli firewall detail --- SoftLayer/CLI/firewall/detail.py | 16 ++++++++++++++-- SoftLayer/managers/firewall.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 1beb1a32a..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,12 +19,24 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) + result = mgr.get_instance(firewall_id) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result['id']]) + table.add_row(['primaryIpAddress', result['primaryIpAddress']]) + table.add_row(['datacenter', result['datacenter']['longName']]) + table.add_row(['networkVlan', result['networkVlan']['name']]) + table.add_row(['networkVlaniD', result['networkVlan']['id']]) + if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - - env.fout(get_rules_table(rules)) + table.add_row(['rules', get_rules_table(rules)]) + env.fout(table) def get_rules_table(rules): diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 34b197521..633eddfd8 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -290,3 +290,15 @@ def edit_standard_fwl_rules(self, firewall_id, rules): template = {'networkComponentFirewallId': firewall_id, 'rules': rules} return rule_svc.createObject(template) + + def get_instance(self, firewall_id, mask=None): + """Get the firewall information + + :param integer firewall_id: the instance ID of the standard firewall + """ + if not mask: + mask = ('mask[datacenter,networkVlan]') + + svc = self.client['Network_Vlan_Firewall'] + + return svc.getObject(id=firewall_id, mask=mask) From 319121eec26ca1402f92ea870f873dec26e6a309 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 16:41:18 -0400 Subject: [PATCH 0098/1050] fix tox tool --- .../SoftLayer_Network_Vlan_Firewall.py | 7 +++ tests/CLI/modules/firewall_tests.py | 47 ++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py index 5d78cf53b..c2f4134f3 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py @@ -7,10 +7,17 @@ }, "id": 3130, "primaryIpAddress": "192.155.239.146", + "datacenter": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, "networkVlan": { "accountId": 307608, "id": 371028, "primarySubnetId": 536252, + "name": 'testvlan', "vlanNumber": 1489, "firewallInterfaces": [ { diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..3fe9c3214 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -59,27 +59,32 @@ def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'#': 1, - 'action': 'permit', - 'dest': 'any on server:80-80', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}, - {'#': 2, - 'action': 'permit', - 'dest': 'any on server:1-65535', - 'dest_mask': '255.255.255.255', - 'protocol': 'tmp', - 'src_ip': '193.212.1.10', - 'src_mask': '255.255.255.255'}, - {'#': 3, - 'action': 'permit', - 'dest': 'any on server:80-800', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}]) + {'datacenter': 'Amsterdam 1', + 'id': 3130, + 'networkVlan': 'testvlan', + 'networkVlaniD': 371028, + 'primaryIpAddress': '192.155.239.146', + 'rules': [{'#': 1, + 'action': 'permit', + 'dest': 'any on server:80-80', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}, + {'#': 2, + 'action': 'permit', + 'dest': 'any on server:1-65535', + 'dest_mask': '255.255.255.255', + 'protocol': 'tmp', + 'src_ip': '193.212.1.10', + 'src_mask': '255.255.255.255'}, + {'#': 3, + 'action': 'permit', + 'dest': 'any on server:80-800', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}]}) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_cancel_firewall(self, confirm_mock): From 7b70badb55ed13961fbea639f08fe66fe432259e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 23 Apr 2021 10:40:30 -0400 Subject: [PATCH 0099/1050] update with Christopher method and fix the team code review comments --- SoftLayer/CLI/call_api.py | 17 +++++++++++------ SoftLayer/utils.py | 16 ++++++++-------- tests/CLI/modules/call_api_tests.py | 8 ++++++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cf0a2b871..b07834ed6 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,9 +112,12 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") -@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" - "E.G --orderBy subnets.id default DESC" - " --orderBy subnets.id=ASC") +@click.option('--orderBy', type=click.STRING, + help="To set the sort direction, ASC or DESC can be provided." + "This should be of the form: '--orderBy nested.property' default DESC or " + "'--orderBy nested.property=ASC', e.g. " + " --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -144,6 +147,8 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or --json-filter '{"virtualGuests":{"hostname":{"operation":"^= test"}}}' --limit=10 slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' + slcli call-api Account getVirtualGuests \\ + --orderBy virttualguests.id=ASC """ if _filters and json_filter: @@ -151,10 +156,10 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or object_filter = _build_filters(_filters) if orderby: - _filters = utils.build_filter_orderby(orderby) - object_filter.update(_filters) + orderby = utils.build_filter_orderby(orderby) + object_filter = utils.dict_merge(object_filter, orderby) if json_filter: - object_filter.update(json_filter) + object_filter = utils.dict_merge(json_filter, object_filter) args = [service, method] + list(parameters) kwargs = { diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cce78839c..6b18b6570 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -221,19 +221,19 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): """Builds filters using the filter options passed into the CLI. - Only support fot create filter option orderBy, default value is DESC. + It only supports the orderBy option, the default value is DESC. """ _filters = {} - aux = list(reversed(str(orderby).split('.'))) - for split in aux: + reverse_filter = list(reversed(orderby.split('.'))) + for keyword in reverse_filter: _aux_filter = {} - if str(split).__contains__('='): - _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + if '=' in keyword: + _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter - elif split == list(aux)[0]: - _aux_filter[split] = query_filter_orderby('DESC') + elif keyword == list(reverse_filter)[0]: + _aux_filter[keyword] = query_filter_orderby('DESC') else: - _aux_filter[split] = _filters + _aux_filter[keyword] = _filters _filters = _aux_filter return _filters diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index b98998f29..d00eb4aaf 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -301,7 +301,9 @@ def test_json_filter(self): def test_call_api_orderBy(self): result = self.run_command(['call-api', 'Account', 'getVirtualGuests', - '--orderBy', 'virtualGuests.id=DESC']) + '--orderBy', 'virtualGuests.id=DESC', + '--mask=virtualGuests.typeId,maxCpu', + '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', @@ -311,4 +313,6 @@ def test_call_api_orderBy(self): 'operation': 'orderBy', 'options': [{ 'name': 'sort', - 'value': ['DESC']}]}}}) + 'value': ['DESC']}]}, + 'typeId': {'operation': 1}} + }) From fbe3b0387c22e0e24cce5424520dc8aa84d73b63 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:33:18 -0500 Subject: [PATCH 0100/1050] #1474 replaced using 'mock' with 'unitest.mock' --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/core_tests.py | 2 +- tests/CLI/environment_tests.py | 2 +- tests/CLI/helper_tests.py | 2 +- tests/CLI/modules/autoscale_tests.py | 2 +- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/config_tests.py | 2 +- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/dns_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- tests/CLI/modules/firewall_tests.py | 2 +- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/loadbal_tests.py | 2 +- tests/CLI/modules/object_storage_tests.py | 2 +- tests/CLI/modules/securitygroup_tests.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/CLI/modules/sshkey_tests.py | 2 +- tests/CLI/modules/ssl_tests.py | 2 +- tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/tag_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 2 +- tests/CLI/modules/user_tests.py | 2 +- tests/CLI/modules/vlan_tests.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 2 +- tests/CLI/modules/vs/vs_placement_tests.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 2 +- tests/api_tests.py | 2 +- tests/config_tests.py | 2 +- tests/decoration_tests.py | 2 +- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- tests/managers/network_tests.py | 2 +- tests/managers/ordering_tests.py | 2 +- tests/managers/user_tests.py | 2 +- tests/managers/vs/vs_capacity_tests.py | 2 +- tests/managers/vs/vs_order_tests.py | 2 +- tests/managers/vs/vs_placement_tests.py | 2 +- tests/managers/vs/vs_tests.py | 2 +- tests/managers/vs/vs_waiting_for_ready_tests.py | 2 +- tests/transport_tests.py | 2 +- 40 files changed, 40 insertions(+), 40 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 563b02494..a9054e3bb 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -11,7 +11,7 @@ import unittest from click import testing -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index f230a3513..e8720514d 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -8,7 +8,7 @@ import logging import click -import mock +from unittest import mock as mock from requests.models import Response import SoftLayer diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..ed393afde 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -6,7 +6,7 @@ """ import click -import mock +from unittest import mock as mock from SoftLayer.CLI import environment from SoftLayer import testing diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index c22278c34..e3a6218c2 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -11,7 +11,7 @@ import tempfile import click -import mock +from unittest import mock as mock from SoftLayer.CLI import core from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6d0e543da..6a1e9e37c 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,7 +7,7 @@ Tests for the autoscale cli command """ -import mock +from unittest import mock as mock import sys from SoftLayer import fixtures diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 83cba03d0..c38a7544d 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -10,7 +10,7 @@ import json -import mock +from unittest import mock as mock class BlockTests(testing.TestCase): diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 5fe917c1c..ef16edf38 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import auth diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 077c3f033..a3199a744 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 82403d1a9..8fb8714f0 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -8,7 +8,7 @@ import os.path import sys -import mock +from unittest import mock as mock from SoftLayer.CLI.dns import zone_import from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1bfe58e16..442ca067d 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class FileTests(testing.TestCase): diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..d80605d81 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 6f2ee40d5..97633c0b6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index b2da4c374..8576a8292 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -3,7 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 2e843906d..b5a219f62 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/securitygroup_tests.py b/tests/CLI/modules/securitygroup_tests.py index b6801fcc8..2c930df71 100644 --- a/tests/CLI/modules/securitygroup_tests.py +++ b/tests/CLI/modules/securitygroup_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import testing diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..198160000 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,7 +8,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/sshkey_tests.py b/tests/CLI/modules/sshkey_tests.py index 253309c08..61260a2f0 100644 --- a/tests/CLI/modules/sshkey_tests.py +++ b/tests/CLI/modules/sshkey_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/ssl_tests.py b/tests/CLI/modules/ssl_tests.py index 79b04df41..2bcdff5ca 100644 --- a/tests/CLI/modules/ssl_tests.py +++ b/tests/CLI/modules/ssl_tests.py @@ -7,7 +7,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class SslTests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 57e7dbbb4..6d7a9bbaa 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock import SoftLayer diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index b2e29721e..364201181 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,7 +4,7 @@ Tests for the user cli command """ -import mock +from unittest import mock as mock from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import testing diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 7b2363eab..92bd848d6 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 2f4c1c978..a11a94838 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,7 @@ import sys import unittest -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ee606f513..73c1fab97 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 2ad0f8647..9d644aabd 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import tempfile diff --git a/tests/CLI/modules/vs/vs_placement_tests.py b/tests/CLI/modules/vs/vs_placement_tests.py index 3b716a6cd..aadf20426 100644 --- a/tests/CLI/modules/vs/vs_placement_tests.py +++ b/tests/CLI/modules/vs/vs_placement_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index de3a310b9..608b9dd6f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -7,7 +7,7 @@ import json import sys -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest diff --git a/tests/api_tests.py b/tests/api_tests.py index 39f596b3c..a83246858 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer import SoftLayer.API diff --git a/tests/config_tests.py b/tests/config_tests.py index f6adb1be6..c4abdb032 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import config from SoftLayer import testing diff --git a/tests/decoration_tests.py b/tests/decoration_tests.py index 9d230671c..d7a39d757 100644 --- a/tests/decoration_tests.py +++ b/tests/decoration_tests.py @@ -6,7 +6,7 @@ """ import logging -import mock +from unittest import mock as mock from SoftLayer.decoration import retry from SoftLayer import exceptions diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 6888db3ce..ea0efeb42 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..3fbdf5740 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -6,7 +6,7 @@ """ import copy -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 80d054f9d..735492b9b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import unittest diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 5f88d59d5..53995264d 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 61f5b4d0a..5ea4d2696 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -5,7 +5,7 @@ """ import datetime -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index c6aad9f56..6fa6599e8 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/vs/vs_order_tests.py b/tests/managers/vs/vs_order_tests.py index 7b54f5450..ae77df2cd 100644 --- a/tests/managers/vs/vs_order_tests.py +++ b/tests/managers/vs/vs_order_tests.py @@ -6,7 +6,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_placement_tests.py b/tests/managers/vs/vs_placement_tests.py index b492f69bf..7a6a69457 100644 --- a/tests/managers/vs/vs_placement_tests.py +++ b/tests/managers/vs/vs_placement_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.managers.vs_placement import PlacementManager from SoftLayer import testing diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index d5202bbe8..976a66fd8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_waiting_for_ready_tests.py b/tests/managers/vs/vs_waiting_for_ready_tests.py index 4308bd55d..08b3071c6 100644 --- a/tests/managers/vs/vs_waiting_for_ready_tests.py +++ b/tests/managers/vs/vs_waiting_for_ready_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 27f892098..854ee6b2e 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -8,7 +8,7 @@ import warnings import json -import mock +from unittest import mock as mock import pytest import requests From f8e88bc83190548020479acd4f046c1214a00a27 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:34:24 -0500 Subject: [PATCH 0101/1050] autopep8 changes --- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/server_tests.py | 36 +++++++++++------------ tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 1 + tests/CLI/modules/vs/vs_create_tests.py | 34 ++++++++++----------- tests/CLI/modules/vs/vs_tests.py | 39 +++++++++++++------------ tests/managers/hardware_tests.py | 8 ++--- tests/managers/ordering_tests.py | 9 +++--- 8 files changed, 67 insertions(+), 64 deletions(-) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 97633c0b6..e12b7c3f6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -79,7 +79,7 @@ def test_create(self, confirm_mock): { "item": "Total monthly cost", "cost": "2.00" - }]) + }]) def test_ip_unassign(self): result = self.run_command(['globalip', 'unassign', '1']) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 198160000..d39af4571 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -692,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -747,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 6d7a9bbaa..b73529a4f 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -173,7 +173,7 @@ def test_lookup(self): "netmask": "255.255.255.192", "gateway": "10.47.16.129", "type": "PRIMARY" - }}) + }}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_cancel(self, confirm_mock): diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 92bd848d6..4fbdbff0c 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -15,6 +15,7 @@ class FakeTTY(): """A fake object to fake STD input""" + def __init__(self, isatty=False, read="Default Output"): """Sets isatty and read""" self._isatty = isatty diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 9d644aabd..52625d337 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -113,26 +113,26 @@ def test_create_by_router(self, confirm_mock): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') args = ({ - 'startCpus': 2, - 'maxMemory': 1024, - 'hostname': 'host', - 'domain': 'example.com', - 'localDiskFlag': True, - 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': {'bootMode': None}, - 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': {'name': 'dal05'}, - 'primaryBackendNetworkComponent': { + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { 'router': { 'id': 577940 } - }, - 'primaryNetworkComponent': { - 'router': { - 'id': 1639255 - } - } - },) + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 608b9dd6f..97dc52ea4 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -322,7 +322,8 @@ def test_create_options_prices(self): self.assert_no_fail(result) def test_create_options_prices_location(self): - result = self.run_command(['vs', 'create-options', '--prices', 'dal13', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + result = self.run_command(['vs', 'create-options', '--prices', 'dal13', + '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -344,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -399,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 3fbdf5740..fa5459cd1 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 53995264d..b25c42494 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -744,7 +744,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -761,7 +761,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -779,7 +779,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -848,7 +848,8 @@ def test_resolve_location_name_invalid(self): self.assertIn("Invalid location", str(exc)) def test_resolve_location_name_not_exist(self): - exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") + exc = self.assertRaises(exceptions.SoftLayerError, + self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) # https://github.com/softlayer/softlayer-python/issues/1425 From 09f86f435277bdee5379fe2586a3e97905d850e2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:54:02 -0500 Subject: [PATCH 0102/1050] fixing import order stuff --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/modules/autoscale_tests.py | 3 ++- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/server_tests.py | 7 +++---- tests/CLI/modules/subnet_tests.py | 8 ++++---- tests/CLI/modules/vs/vs_create_tests.py | 3 ++- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/ipsec_tests.py | 3 +-- tests/managers/network_tests.py | 3 ++- tests/transport_tests.py | 4 ++-- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index a9054e3bb..6eff9851c 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -9,9 +9,9 @@ import logging import os.path import unittest +from unittest import mock as mock from click import testing -from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6a1e9e37c..cb3cdfdb9 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,8 +7,9 @@ Tests for the autoscale cli command """ -from unittest import mock as mock + import sys +from unittest import mock as mock from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index a3199a744..a015fa70d 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -6,8 +6,8 @@ """ import json from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Virtual_DedicatedHost diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d39af4571..e6cb2b18a 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,16 +8,15 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock +import json import sys +import tempfile +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -import json -import tempfile - class ServerCLITests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index b73529a4f..65a4cc5c8 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.fixtures import SoftLayer_Product_Order -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer import testing - import json from unittest import mock as mock + import SoftLayer +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing class SubnetTests(testing.TestCase): diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 52625d337..e9bb2fd74 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import tempfile +from unittest import mock as mock from SoftLayer.fixtures import SoftLayer_Product_Package as SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index ea0efeb42..afe3df3a2 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -5,8 +5,8 @@ :license: MIT, see LICENSE for more details. """ from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer import exceptions from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index aaebc9f7f..f88e33ed5 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -4,8 +4,7 @@ :license: MIT, see LICENSE for more details. """ - -from mock import MagicMock +from unittest.mock import MagicMock as MagicMock import SoftLayer from SoftLayer.exceptions import SoftLayerAPIError diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 735492b9b..2463ed999 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import unittest +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 854ee6b2e..ca3dcc73b 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ import io -import warnings - import json from unittest import mock as mock +import warnings + import pytest import requests From dceab111688fe6bccd3b04a96d1db6d400829c41 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:17:57 -0500 Subject: [PATCH 0103/1050] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b1fa2c870..73b9a0007 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs +envlist = py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From 1039f158b13fd5a9f5725e68285e5e5b651188cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:28:35 -0500 Subject: [PATCH 0104/1050] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- .github/workflows/tests.yml | 2 +- setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7bc787791..7d6d35a67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5,3.6,3.7,3.8,3.9] + python-version: [3.6,3.7,3.8,3.9] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index a09cca0a4..c040d9ca7 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', From fda7e0a4371f5f245f79512e84c76e8d8f47fdcd Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:05:23 -0400 Subject: [PATCH 0105/1050] fix the tox tool --- tests/CLI/modules/call_api_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index d00eb4aaf..a22597488 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -305,8 +305,7 @@ def test_call_api_orderBy(self): '--mask=virtualGuests.typeId,maxCpu', '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Account', - 'getVirtualGuests', + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': {'id': { From 5ca3bdee066028def3d926d0a78bafdf527c910f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:27:00 -0400 Subject: [PATCH 0106/1050] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..23b43e936 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,6 +35,7 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) + table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From c8893fd6aa20ec8efa44cf712fc210de8971c7ee Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:29:35 -0400 Subject: [PATCH 0107/1050] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 23b43e936..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,7 +35,6 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From 8de6d4a51d8c83743054290ce4954d8543c1f0ef Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:36:52 -0500 Subject: [PATCH 0108/1050] moved the test_pypi workflow to only trigger on test-pypi branch as we don't have access to the test environment yet --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 5e7c6b683..f3b39abf9 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ test-pypi ] jobs: build-n-publish: From bdade312228af1c17a3ca0a4440648b35cc8e2aa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:37:09 -0500 Subject: [PATCH 0109/1050] Changelog for v5.9.4 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ab66753..f55340957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Change Log +## [5.9.4] - 2021-04-27 +https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 + +#### New Commands +- `slcli hw authorize-storage` #1439 +- `slcli order quote-save` #1451 + + +#### Improvements + +- Refactored managers.ordering_manager.verify_quote() to work better with the REST endpoing #1430 +- Add routers for each DC in slcli hw create-options #1432 +- Add preset datatype in slcli virtual detail #1435 +- Add upgrade option to slcli hw. #1437 +- Ibmcloud authentication support #1315 / #1447 + + `slcli config setup --ibmid` + + `slcli config setup --sso` + + `slcli config setup --cloud_key` + + `slcli config setup --classic_key` +- Refactor slcli hw detail prices. #1443 +- Updated contributing guide #1458 +- Add the Hardware components on "slcli hardware detail" #1452 +- Add billing and lastTransaction on hardware detail #1446 +- Forced reserved capacity guests to be monthly #1454 +- Removing the rwhois commands #1456 +- Added automation to publish to test-pypi #1467 +- Updating author_email to SLDN distro list #1469 +- Add the option to add and upgrade the hw disk. #1455 +- Added a utility to merge objectFilters, #1468 +- Fixes shift+ins when pasteing into a password field for windows users. #1460 +- Add Billing and lastTransaction on slcli virtual detail #1466 +- Fixing 'import mock' pylint issues #1476 + ## [5.9.3] - 2021-03-03 https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 71c52be75..cec2831e0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.3' +VERSION = 'v5.9.4' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c040d9ca7..48c1e4e6c 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.3', + version='5.9.4', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 486b0d068911c5d3214874796261fc298e2ce680 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:52:37 -0500 Subject: [PATCH 0110/1050] fixed a pylint issue --- SoftLayer/CLI/sshkey/add.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index a3a3c6ccb..d80330d7c 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,9 +33,9 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - key_file = open(path.expanduser(in_file), 'rU') - key_text = key_file.read().strip() - key_file.close() + with open(path.expanduser(in_file), 'rU') as key_file: + key_text = key_file.read().strip() + key_file.close() mgr = SoftLayer.SshKeyManager(env.client) result = mgr.add_key(key_text, label, note) From 9a8fcf8079b73cd8883e6f57eaf40543eac4c499 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 27 Apr 2021 12:37:02 -0400 Subject: [PATCH 0111/1050] #1449 add image detail transaction data --- SoftLayer/CLI/image/__init__.py | 3 ++- SoftLayer/CLI/image/detail.py | 46 +++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/image/__init__.py b/SoftLayer/CLI/image/__init__.py index b0734db99..17d836625 100644 --- a/SoftLayer/CLI/image/__init__.py +++ b/SoftLayer/CLI/image/__init__.py @@ -5,7 +5,8 @@ MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' 'imageType') -DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' +DETAIL_MASK = MASK + (',firstChild,children[id,blockDevicesDiskSpaceTotal,datacenter,' + 'transaction[transactionGroup,transactionStatus]],' 'note,createDate,status,transaction') PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') diff --git a/SoftLayer/CLI/image/detail.py b/SoftLayer/CLI/image/detail.py index 5bba1beac..865f95a7d 100644 --- a/SoftLayer/CLI/image/detail.py +++ b/SoftLayer/CLI/image/detail.py @@ -19,14 +19,10 @@ def cli(env, identifier): image_mgr = SoftLayer.ImageManager(env.client) image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') - image = image_mgr.get_image(image_id, mask=image_mod.DETAIL_MASK) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + children_images = image.get('children') + total_size = utils.lookup(image, 'firstChild', 'blockDevicesDiskSpaceTotal') or 0 table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -40,9 +36,10 @@ def cli(env, identifier): utils.lookup(image, 'status', 'keyname'), utils.lookup(image, 'status', 'name'), )]) + table.add_row([ 'active_transaction', - formatting.transaction_status(image.get('transaction')), + formatting.listing(_get_transaction_groups(children_images), separator=','), ]) table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['visibility', @@ -56,8 +53,35 @@ def cli(env, identifier): table.add_row(['flex', image.get('flexImageFlag')]) table.add_row(['note', image.get('note')]) table.add_row(['created', image.get('createDate')]) - table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(sorted(datacenters), - separator=',')]) + table.add_row(['total_size', formatting.b_to_gb(total_size)]) + table.add_row(['datacenters', _get_datacenter_table(children_images)]) env.fout(table) + + +def _get_datacenter_table(children_images): + """Returns image details as datacenter, size, and transaction within a formatting table. + + :param children_images: A list of images. + """ + table_datacenter = formatting.Table(['DC', 'size', 'transaction']) + for child in children_images: + table_datacenter.add_row([ + utils.lookup(child, 'datacenter', 'name'), + formatting.b_to_gb(child.get('blockDevicesDiskSpaceTotal', 0)), + formatting.transaction_status(child.get('transaction')) + ]) + + return table_datacenter + + +def _get_transaction_groups(children_images): + """Returns a Set of transaction groups. + + :param children_images: A list of images. + """ + transactions = set() + for child in children_images: + transactions.add(utils.lookup(child, 'transaction', 'transactionGroup', 'name')) + + return transactions From 661a2258de89c0608abef252c713707934c36192 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 16:37:45 -0400 Subject: [PATCH 0112/1050] Fix to Christopher code review comments --- SoftLayer/managers/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 633eddfd8..44eef25ab 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -297,7 +297,7 @@ def get_instance(self, firewall_id, mask=None): :param integer firewall_id: the instance ID of the standard firewall """ if not mask: - mask = ('mask[datacenter,networkVlan]') + mask = 'mask[datacenter,networkVlan]' svc = self.client['Network_Vlan_Firewall'] From aece6185968a6754636476a35b067ca269e3a578 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:27:16 -0400 Subject: [PATCH 0113/1050] Fix to Christopher code review comments --- tests/CLI/modules/firewall_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 59c1a5a1a..f248b16f1 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -58,6 +58,8 @@ def test_add_server(self, confirm_mock): def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) + json_result = json.loads(result.output) + self.assertEqual(json_result['rules'][0]['action'], 'permit') self.assertEqual(json.loads(result.output), {'datacenter': 'Amsterdam 1', 'id': 3130, From 66bc9adb655f912082dd9a45ac8ef9327abd2d63 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:42:35 -0400 Subject: [PATCH 0114/1050] Fix to Christopher code review comments --- SoftLayer/CLI/firewall/detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..afdf13b53 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -25,11 +25,11 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', result['id']]) - table.add_row(['primaryIpAddress', result['primaryIpAddress']]) - table.add_row(['datacenter', result['datacenter']['longName']]) - table.add_row(['networkVlan', result['networkVlan']['name']]) - table.add_row(['networkVlaniD', result['networkVlan']['id']]) + table.add_row(['id', utils.lookup(result, 'id')]) + table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From d53dc43b35207cf7e1081e3d9fa7c7fb6221fdef Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 18:02:07 -0400 Subject: [PATCH 0115/1050] Fix to team code review comments --- tests/CLI/modules/call_api_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index a22597488..03c861d2a 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -308,10 +308,11 @@ def test_call_api_orderBy(self): self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': - {'id': { - 'operation': 'orderBy', - 'options': [{ - 'name': 'sort', - 'value': ['DESC']}]}, + {'id': + { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}, 'typeId': {'operation': 1}} }) From 39b3b2a758d9dbd1eb81612b6d443dc13d0d5d32 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 30 Apr 2021 08:59:21 -0400 Subject: [PATCH 0116/1050] Fix to team code review comments --- SoftLayer/CLI/firewall/detail.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index afdf13b53..647715a3d 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,17 +19,17 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) - result = mgr.get_instance(firewall_id) + _firewall = mgr.get_instance(firewall_id) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', utils.lookup(result, 'id')]) - table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) - table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) - table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) - table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) + table.add_row(['id', _firewall.get('id')]) + table.add_row(['primaryIpAddress', _firewall.get('primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(_firewall, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(_firewall, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From bdccfa963a07968b53dd72c26ab2405fbafb848a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 09:53:58 -0400 Subject: [PATCH 0117/1050] add new email feature --- SoftLayer/CLI/email/__init__.py | 0 SoftLayer/CLI/email/detail.py | 72 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 + SoftLayer/fixtures/SoftLayer_Account.py | 24 +++++++ ...Network_Message_Delivery_Email_Sendgrid.py | 27 +++++++ SoftLayer/managers/email.py | 47 ++++++++++++ docs/api/managers/email.rst | 5 ++ docs/cli/email.rst | 9 +++ tests/CLI/modules/email_tests.py | 15 ++++ tests/managers/email_tests.py | 26 +++++++ 10 files changed, 228 insertions(+) create mode 100644 SoftLayer/CLI/email/__init__.py create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py create mode 100644 SoftLayer/managers/email.py create mode 100644 docs/api/managers/email.rst create mode 100644 docs/cli/email.rst create mode 100644 tests/CLI/modules/email_tests.py create mode 100644 tests/managers/email_tests.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..44a4bbbdb --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,72 @@ +"""Get details for an image.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """""" + manager = AccountManager(env.client) + email_manager = EmailManager(env.client) + result = manager.get_Network_Message_Delivery_Accounts() + + table = formatting.KeyValueTable(['name', 'value']) + + table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) + table_information.align['id'] = 'r' + table_information.align['username'] = 'l' + + for email in result: + # print(email['id']) + table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), + utils.lookup(email, 'type', 'description'), + utils.lookup(email, 'vendor', 'keyName')]) + + overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id'), + ["requests", "delivered", "opens", "clicks", "bounds"], + True, True, True, 6) + + table.add_row(['email information', table_information]) + table.add_row(['email overview', overview_table]) + for statistic in statistics: + table.add_row(['statistics', _build_statistics_table(statistic)]) + + env.fout(table) + + +def _build_overview_table(email_overview): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) + table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) + table.add_row(['package', email_overview.get('package')]) + table.add_row(['reputation', email_overview.get('reputation')]) + table.add_row(['requests', email_overview.get('requests')]) + + return table + + +def _build_statistics_table(statistics): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['delivered', statistics.get('delivered')]) + table.add_row(['requests', statistics.get('requests')]) + table.add_row(['bounces', statistics.get('bounces')]) + table.add_row(['opens', statistics.get('opens')]) + table.add_row(['clicks', statistics.get('clicks')]) + table.add_row(['spam Reports', statistics.get('spamReports')]) + + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..0c6f322b7 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -119,6 +119,9 @@ ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), + ('email', 'SoftLayer.CLI.email'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4261c32a7..4cfafa30f 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1076,3 +1076,27 @@ "username": "SL01SEV1234567_111" } ] + +getNetworkMessageDeliveryAccounts = [ + { + "accountId": 147258, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "typeId": 21, + "username": "test_CLI@ie.ibm.com", + "vendorId": 1, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "test_CLI@ie.ibm.com", + "smtpAccess": "1" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py new file mode 100644 index 000000000..94fd169fd --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -0,0 +1,27 @@ +getAccountOverview = { + "creditsAllowed": 25000, + "creditsOverage": 0, + "creditsRemain": 25000, + "creditsUsed": 0, + "package": "Free Package", + "reputation": 100, + "requests": 56 +} + +getStatistics = [{ + "blocks": 0, + "bounces": 0, + "clicks": 0, + "date": "2021-04-28", + "delivered": 0, + "invalidEmail": 0, + "opens": 0, + "repeatBounces": 0, + "repeatSpamReports": 0, + "repeatUnsubscribes": 0, + "requests": 0, + "spamReports": 0, + "uniqueClicks": 0, + "uniqueOpens": 0, + "unsubscribes": 0 +}] diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py new file mode 100644 index 000000000..5b5f49e00 --- /dev/null +++ b/SoftLayer/managers/email.py @@ -0,0 +1,47 @@ +""" + SoftLayer.account + ~~~~~~~~~~~~~~~~~~~~~~~ + Account manager + + :license: MIT, see License for more details. +""" + +from SoftLayer import utils + + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + + +class EmailManager(utils.IdentifierMixin, object): + """Common functions for getting information from the Account service + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.client = client + + def get_AccountOverview(self, identifier): + """Gets all the Network Message Delivery Account Overview + + :returns: Network Message Delivery Account overview + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview', id=identifier) + + def get_statistics(self, identifier, selectedStatistics, + startDate, endDate, aggregatesOnly, days): + """Gets statistics Network Message Delivery Account + + :returns: statistics Network Message Delivery Account + """ + body = [selectedStatistics, + startDate, + endDate, + aggregatesOnly, + days + ] + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics', id=identifier, *body) diff --git a/docs/api/managers/email.rst b/docs/api/managers/email.rst new file mode 100644 index 000000000..45d839c16 --- /dev/null +++ b/docs/api/managers/email.rst @@ -0,0 +1,5 @@ +.. _email: + +.. automodule:: SoftLayer.managers.email + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/email.rst b/docs/cli/email.rst new file mode 100644 index 000000000..67d5319db --- /dev/null +++ b/docs/cli/email.rst @@ -0,0 +1,9 @@ +.. _cli_email: + +Email Commands +================= + + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py new file mode 100644 index 000000000..db085efbf --- /dev/null +++ b/tests/CLI/modules/email_tests.py @@ -0,0 +1,15 @@ +""" + SoftLayer.tests.CLI.modules.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer import testing + + +class EmailCLITests(testing.TestCase): + + def test_detail(self): + result = self.run_command(['email', 'detail']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py new file mode 100644 index 000000000..b573326fa --- /dev/null +++ b/tests/managers/email_tests.py @@ -0,0 +1,26 @@ +""" + SoftLayer.tests.managers.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" + +from SoftLayer.managers.email import EmailManager +from SoftLayer import testing + + +class AccountManagerTests(testing.TestCase): + + def test_get_AccountOverview(self): + self.manager = EmailManager(self.client) + self.manager.get_AccountOverview(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview') + + def test_get_statistics(self): + self.manager = EmailManager(self.client) + self.manager.get_statistics(1232123, + ["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, True, 6) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics') From dc660d8a548562f6dd9026cec550a465696efbfa Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:09:45 -0400 Subject: [PATCH 0118/1050] fix the tp --- SoftLayer/managers/account.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d008b92a3..a613be0cc 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -293,3 +293,13 @@ def get_routers(self, mask=None, location=None): } return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) + + def get_Network_Message_Delivery_Accounts(self): + """Gets all Network Message delivery accounts. + + :returns: Network Message delivery accounts + """ + + _mask = """vendor,type""" + + return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) From 6cce16c6c9ad594449a3db5af702824806ec5f17 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:22:45 -0400 Subject: [PATCH 0119/1050] fix tox tool --- SoftLayer/CLI/email/detail.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 44a4bbbdb..bdc4161a6 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -13,7 +13,7 @@ @click.command() @environment.pass_env def cli(env): - """""" + """Display the Email Delivery account informatino """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) result = manager.get_Network_Message_Delivery_Accounts() @@ -25,7 +25,6 @@ def cli(env): table_information.align['username'] = 'l' for email in result: - # print(email['id']) table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) From 00131b22f81f8daca3c3b0ebb23c8716a03ac963 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 19:00:21 -0400 Subject: [PATCH 0120/1050] update the conflicts merge --- SoftLayer/CLI/hardware/detail.py | 25 +++++++++++-- .../fixtures/SoftLayer_Hardware_Server.py | 18 ++++++++++ SoftLayer/managers/hardware.py | 35 +++++++++++++++++++ tests/CLI/modules/server_tests.py | 4 +++ tests/managers/hardware_tests.py | 20 +++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index da62e5de6..feee666cf 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -9,13 +9,16 @@ from SoftLayer.CLI import helpers from SoftLayer import utils +# pylint: disable=R0915 + @click.command() @click.argument('identifier') @click.option('--passwords', is_flag=True, help='Show passwords (check over your shoulder!)') @click.option('--price', is_flag=True, help='Show associated prices') +@click.option('--components', is_flag=True, default=False, help='Show associated hardware components') @environment.pass_env -def cli(env, identifier, passwords, price): +def cli(env, identifier, passwords, price, components): """Get details for a hardware device.""" hardware = SoftLayer.HardwareManager(env.client) @@ -66,7 +69,7 @@ def cli(env, identifier, passwords, price): utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) table.add_row(['last_transaction', last_transaction]) - table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: @@ -107,6 +110,24 @@ def cli(env, identifier, passwords, price): pass_table.add_row([item['username'], item['password']]) table.add_row(['remote users', pass_table]) + if components: + components = hardware.get_components(identifier) + components_table = formatting.Table(['name', 'Firmware version', 'Firmware build date', 'Type']) + components_table.align['date'] = 'l' + component_ids = [] + for hw_component in components: + if hw_component['id'] not in component_ids: + firmware = hw_component['hardwareComponentModel']['firmwares'][0] + components_table.add_row([utils.lookup(hw_component, 'hardwareComponentModel', 'longDescription'), + utils.lookup(firmware, 'version'), + utils.clean_time(utils.lookup(firmware, 'createDate')), + utils.lookup(hw_component, 'hardwareComponentModel', + 'hardwareGenericComponentModel', 'hardwareComponentType', + 'keyName')]) + component_ids.append(hw_component['id']) + + table.add_row(['components', components_table]) + table.add_row(['tags', formatting.tags(result['tagReferences'])]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 0eacab158..938c5cebc 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -377,3 +377,21 @@ } } ] + +getComponents = [{ + "hardwareComponentModelId": 147, + "hardwareId": 1234, + "id": 369, + "modifyDate": "2017-11-10T16:59:38-06:00", + "serviceProviderId": 1, + "hardwareComponentModel": { + "name": "IMM2 - Onboard", + "firmwares": [ + { + "createDate": "2020-09-24T13:46:29-06:00", + "version": "5.60" + }, + { + "createDate": "2019-10-14T16:51:12-06:00", + "version": "5.10" + }]}}] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..2fec42a82 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1030,6 +1030,41 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t return disk_price + def get_components(self, hardware_id, mask=None, filter_component=None): + """Get details about a hardware components. + + :param int hardware_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified components. + """ + if not mask: + mask = 'id,hardwareComponentModel[longDescription,' \ + 'hardwareGenericComponentModel[description,hardwareComponentType[keyName]],' \ + 'firmwares[createDate,version]]' + + if not filter_component: + filter_component = {"components": { + "hardwareComponentModel": { + "firmwares": { + "createDate": { + "operation": "orderBy", + "options": [ + { + "name": "sort", + "value": [ + "DESC" + ] + }, + { + "name": "sortOrder", + "value": [ + 1 + ]}]} + }}}} + + return self.client.call('Hardware_Server', 'getComponents', + mask=mask, filter=filter_component, id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..4b286b351 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -985,3 +985,7 @@ def test_upgrade(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + + def test_components(self): + result = self.run_command(['hardware', 'detail', '100', '--components']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..7fcac5202 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -928,6 +928,26 @@ def test_upgrade_full(self): self.assertIn({'id': 22482}, order_container['prices']) self.assertIn({'id': 50357}, order_container['prices']) + def test_get_components(self): + result = self.hardware.get_components(1234) + components = [{'hardwareComponentModelId': 147, + 'hardwareId': 1234, + 'id': 369, + 'modifyDate': '2017-11-10T16:59:38-06:00', + 'serviceProviderId': 1, + 'hardwareComponentModel': + {'name': 'IMM2 - Onboard', + 'firmwares': + [{'createDate': '2020-09-24T13:46:29-06:00', + 'version': '5.60'}, + {'createDate': '2019-10-14T16:51:12-06:00', + 'version': '5.10'}]}}] + self.assert_called_with('SoftLayer_Hardware_Server', 'getComponents') + self.assertEqual(result, components) + self.assertEqual(result[0]['hardwareId'], 1234) + self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') + self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + class HardwareHelperTests(testing.TestCase): From 180f7a1f795644815871b2bb2b4e4499b578b742 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 6 May 2021 18:38:14 -0400 Subject: [PATCH 0121/1050] fix team code review comments --- SoftLayer/CLI/email/{detail.py => list.py} | 16 +++++++--------- SoftLayer/CLI/routes.py | 2 +- SoftLayer/managers/account.py | 2 +- SoftLayer/managers/email.py | 22 +++++++++++----------- tests/CLI/modules/email_tests.py | 2 +- tests/managers/email_tests.py | 9 +++------ 6 files changed, 24 insertions(+), 29 deletions(-) rename SoftLayer/CLI/email/{detail.py => list.py} (82%) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/list.py similarity index 82% rename from SoftLayer/CLI/email/detail.py rename to SoftLayer/CLI/email/list.py index bdc4161a6..05c096250 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get details for an image.""" +"""Get lists Email Delivery account Service """ # :license: MIT, see LICENSE for more details. import click @@ -13,10 +13,10 @@ @click.command() @environment.pass_env def cli(env): - """Display the Email Delivery account informatino """ + """Lists Email Delivery Service """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) - result = manager.get_Network_Message_Delivery_Accounts() + result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) @@ -29,10 +29,8 @@ def cli(env): utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) - overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) - statistics = email_manager.get_statistics(email.get('id'), - ["requests", "delivered", "opens", "clicks", "bounds"], - True, True, True, 6) + overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id')) table.add_row(['email information', table_information]) table.add_row(['email overview', overview_table]) @@ -43,7 +41,7 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' @@ -57,7 +55,7 @@ def _build_overview_table(email_overview): def _build_statistics_table(statistics): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 0c6f322b7..6037fa265 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -120,7 +120,7 @@ ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('email', 'SoftLayer.CLI.email'), - ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:list', 'SoftLayer.CLI.email.list:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index a613be0cc..b7398076d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -294,7 +294,7 @@ def get_routers(self, mask=None, location=None): return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) - def get_Network_Message_Delivery_Accounts(self): + def get_network_message_delivery_accounts(self): """Gets all Network Message delivery accounts. :returns: Network Message delivery accounts diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index 5b5f49e00..ce80a9298 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -1,7 +1,7 @@ """ - SoftLayer.account + SoftLayer.email ~~~~~~~~~~~~~~~~~~~~~~~ - Account manager + Email manager :license: MIT, see License for more details. """ @@ -14,7 +14,7 @@ class EmailManager(utils.IdentifierMixin, object): - """Common functions for getting information from the Account service + """Common functions for getting information from the email service :param SoftLayer.API.BaseClient client: the client instance """ @@ -22,7 +22,7 @@ class EmailManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client - def get_AccountOverview(self, identifier): + def get_account_overview(self, identifier): """Gets all the Network Message Delivery Account Overview :returns: Network Message Delivery Account overview @@ -30,16 +30,16 @@ def get_AccountOverview(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview', id=identifier) - def get_statistics(self, identifier, selectedStatistics, - startDate, endDate, aggregatesOnly, days): - """Gets statistics Network Message Delivery Account + def get_statistics(self, identifier, days=30): + """gets statistics from email accounts + :days: range number :returns: statistics Network Message Delivery Account """ - body = [selectedStatistics, - startDate, - endDate, - aggregatesOnly, + body = [["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, + True, days ] diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index db085efbf..798745a7f 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -10,6 +10,6 @@ class EmailCLITests(testing.TestCase): def test_detail(self): - result = self.run_command(['email', 'detail']) + result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b573326fa..b8a2b58b6 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -8,19 +8,16 @@ from SoftLayer import testing -class AccountManagerTests(testing.TestCase): +class EmailManagerTests(testing.TestCase): def test_get_AccountOverview(self): self.manager = EmailManager(self.client) - self.manager.get_AccountOverview(1232123) + self.manager.get_account_overview(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview') def test_get_statistics(self): self.manager = EmailManager(self.client) - self.manager.get_statistics(1232123, - ["requests", "delivered", "opens", "clicks", "bounds"], - True, - True, True, 6) + self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') From f8d5882143e425c3c061442c963d524780ec9ecd Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 7 May 2021 08:54:58 -0400 Subject: [PATCH 0122/1050] add documentation --- docs/cli/email.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 67d5319db..574fba1bc 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -4,6 +4,6 @@ Email Commands ================= -.. click:: SoftLayer.CLI.email.detail:cli - :prog: email detail +.. click:: SoftLayer.CLI.email.list:cli + :prog: email list :show-nested: \ No newline at end of file From 66a3cde4255d742cbd858d758c11352b26075f09 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:15:05 -0400 Subject: [PATCH 0123/1050] #1481 refactor block and file cli to shows the whole notes in a format json output --- SoftLayer/CLI/block/list.py | 28 +++---------------------- SoftLayer/CLI/environment.py | 4 ++++ SoftLayer/CLI/file/list.py | 23 ++------------------ SoftLayer/CLI/storage_utils.py | 38 +++++++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 769aad233..bd14c7282 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -5,8 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -18,7 +17,7 @@ 'storage_type', lambda b: b['storageType']['keyName'].split('_').pop(0) if 'storageType' in b and 'keyName' in b['storageType'] - and isinstance(b['storageType']['keyName'], str) + and isinstance(b['storageType']['keyName'], str) else '-', mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), @@ -52,8 +51,6 @@ 'notes' ] -DEFAULT_NOTES_SIZE = 20 - @click.command() @click.option('--username', '-u', help='Volume username') @@ -78,24 +75,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(block_volumes) - - for block_volume in block_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(block_volume)]) - + table = storage_utils.build_output_table(env, block_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(block_volumes): - """Reduces the size of the notes in a volume list. - - :param block_volumes: An list of block volumes - """ - for block_volume in block_volumes: - if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] - block_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index e16c5cde9..c96f4a7c3 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -52,6 +52,10 @@ def fmt(self, output, fmt=None): fmt = self.format return formatting.format_output(output, fmt) + def format_output_is_json(self): + """Return True if format output is json or jsonraw""" + return 'json' in self.format + def fout(self, output, newline=True): """Format the input and output to the console (stdout).""" if output is not None: diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index f7c08fe18..ba72c4fe0 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -5,7 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -76,24 +76,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(file_volumes) - - for file_volume in file_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(file_volume)]) - + table = storage_utils.build_output_table(env, file_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(file_volumes): - """Reduces the size of the notes in a volume list. - - :param file_volumes: An list of file volumes - """ - for file_volume in file_volumes: - if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] - file_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index 47c888960..aa24585eb 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -2,6 +2,43 @@ # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import formatting + +DEFAULT_NOTES_SIZE = 20 + + +def reduce_notes(volumes, env): + """Reduces all long notes found in the volumes list just if the format output is different from a JSON format. + + :param list volumes: An list of storage volumes + :param env :A environment console. + """ + if env.format_output_is_json(): + return + + for volume in volumes: + if len(volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = volume['notes'][:DEFAULT_NOTES_SIZE] + volume['notes'] = shortened_notes + + +def build_output_table(env, volumes, columns, sortby): + """Builds a formatting table for a list of volumes. + + :param env :A Environment console. + :param list volumes: An list of storage volumes + :param columns :A ColumnFormatter for column names + :param str sortby :A string to sort by. + """ + table = formatting.Table(columns.columns) + if sortby in table.columns: + table.sortby = sortby + + reduce_notes(volumes, env) + for volume in volumes: + table.add_row([value or formatting.blank() + for value in columns.row(volume)]) + return table def _format_name(obj): @@ -96,7 +133,6 @@ def _format_name(obj): """), ] - DEFAULT_COLUMNS = [ 'id', 'name', From c1de463a2b98720107c07484a31687a5b3acdb9b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:16:12 -0400 Subject: [PATCH 0124/1050] #1481 add tests to block and file cli to shows the whole notes in a format json output --- tests/CLI/environment_tests.py | 4 ++++ tests/CLI/modules/block_tests.py | 35 ++++++++++++++++++++++++++++++-- tests/CLI/modules/file_tests.py | 32 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f194bdc87..8829bf93d 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -81,3 +81,7 @@ def test_print_unicode(self, echo): ] self.env.fout(output) self.assertEqual(2, echo.call_count) + + def test_format_output_is_json(self): + self.env.format = 'jsonraw' + self.assertTrue(self.env.format_output_is_json()) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index c38a7544d..1c6b22e14 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerAPIError from SoftLayer import testing - import json from unittest import mock as mock @@ -135,7 +135,7 @@ def test_volume_list(self): 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, - 'notes': "{'status': 'availabl", + 'notes': "{'status': 'available'}", 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', @@ -143,6 +143,37 @@ def test_volume_list(self): }], json.loads(result.output)) + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table)+'\n' + result = self.run_command(['--format', 'table', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + def test_volume_list_order(self): result = self.run_command(['block', 'volume-list', '--order=1234567']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 442ca067d..cbe73818c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerError from SoftLayer import testing @@ -63,6 +64,37 @@ def test_volume_list_order(self): json_result = json.loads(result.output) self.assertEqual(json_result[0]['id'], 1) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table) + '\n' + result = self.run_command(['--format', 'table', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ From 1572d8ceb9865dfec714a9a83cad7043ab544f0d Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 10 May 2021 14:57:08 -0400 Subject: [PATCH 0125/1050] fix team code review comments --- SoftLayer/CLI/email/list.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 05c096250..a5353a31a 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -19,7 +19,8 @@ def cli(env): result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) - + table.align['name'] = 'r' + table.align['value'] = 'l' table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) table_information.align['id'] = 'r' table_information.align['username'] = 'l' @@ -32,8 +33,8 @@ def cli(env): overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) statistics = email_manager.get_statistics(email.get('id')) - table.add_row(['email information', table_information]) - table.add_row(['email overview', overview_table]) + table.add_row(['email_information', table_information]) + table.add_row(['email_overview', overview_table]) for statistic in statistics: table.add_row(['statistics', _build_statistics_table(statistic)]) @@ -41,29 +42,24 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) - table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) - table.add_row(['package', email_overview.get('package')]) - table.add_row(['reputation', email_overview.get('reputation')]) - table.add_row(['requests', email_overview.get('requests')]) + table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('package'), email_overview.get('reputation'), + email_overview.get('requests')]) return table def _build_statistics_table(statistics): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['delivered', statistics.get('delivered')]) - table.add_row(['requests', statistics.get('requests')]) - table.add_row(['bounces', statistics.get('bounces')]) - table.add_row(['opens', statistics.get('opens')]) - table.add_row(['clicks', statistics.get('clicks')]) - table.add_row(['spam Reports', statistics.get('spamReports')]) + table.add_row([statistics.get('delivered'), statistics.get('requests'), + statistics.get('bounces'), statistics.get('opens'), + statistics.get('clicks'), statistics.get('spamReports')]) return table From 7b826af01d80a0150a0732e20ce939a4e5eef233 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 May 2021 09:17:49 -0400 Subject: [PATCH 0126/1050] add new email features, email detail and email edit --- SoftLayer/CLI/email/__init__.py | 1 + SoftLayer/CLI/email/detail.py | 33 +++++++++++++++++++ SoftLayer/CLI/email/edit.py | 32 ++++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ ...Network_Message_Delivery_Email_Sendgrid.py | 31 +++++++++++++++++ SoftLayer/managers/email.py | 30 +++++++++++++++++ docs/cli.rst | 1 + docs/cli/email.rst | 8 +++++ tests/CLI/modules/email_tests.py | 16 ++++++++- tests/managers/email_tests.py | 6 ++++ 10 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/CLI/email/edit.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py index e69de29bb..b765cbe50 100644 --- a/SoftLayer/CLI/email/__init__.py +++ b/SoftLayer/CLI/email/__init__.py @@ -0,0 +1 @@ +"""Network Delivery account""" diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..28fccba57 --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,33 @@ +"""Display details for a specified email.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Display details for a specified email.""" + + email_manager = EmailManager(env.client) + result = email_manager.get_instance(identifier) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result.get('id')]) + table.add_row(['username', result.get('username')]) + table.add_row(['create_date', result.get('createDate')]) + table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) + table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) + table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) + table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py new file mode 100644 index 000000000..321e6306f --- /dev/null +++ b/SoftLayer/CLI/email/edit.py @@ -0,0 +1,32 @@ +"""Edit details of an Delivery email account.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.email import EmailManager + + +@click.command() +@click.argument('identifier') +@click.option('--username', help="username account") +@click.option('--email', help="Additional note for the image") +@click.option('--password', + help="Password must be between 8 and 20 characters " + "and must contain one letter and one number.") +@environment.pass_env +def cli(env, identifier, username, email, password): + """Edit details of an email delivery account.""" + data = {} + if username: + data['username'] = username + if email: + data['emailAddress'] = email + if password: + data['password'] = password + + email_manager = EmailManager(env.client) + + if not email_manager.editObject(identifier, data): + raise exceptions.CLIAbort("Failed to Edit email account") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6037fa265..38a716434 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -121,6 +121,8 @@ ('email', 'SoftLayer.CLI.email'), ('email:list', 'SoftLayer.CLI.email.list:cli'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:edit', 'SoftLayer.CLI.email.edit:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 94fd169fd..9bff8ad08 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -25,3 +25,34 @@ "uniqueOpens": 0, "unsubscribes": 0 }] + +getObject = { + "accountId": 123456, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "password": "Test123456789", + "typeId": 21, + "username": "techsupport3@ie.ibm.com", + "vendorId": 1, + "billingItem": { + "categoryCode": "network_message_delivery", + "description": "Free Package", + "id": 695735054, + "notes": "techsupport3@ie.ibm.com", + }, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "techsupport3@ie.ibm.com", + "smtpAccess": "1" +} + +editObject = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index ce80a9298..df3c144e5 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -45,3 +45,33 @@ def get_statistics(self, identifier, days=30): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics', id=identifier, *body) + + def get_instance(self, identifier): + """Gets the Network_Message_Delivery_Email_Sendgrid instance + + :return: Network_Message_Delivery_Email_Sendgrid + """ + + _mask = """emailAddress,type,billingItem,vendor""" + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject', id=identifier, mask=_mask) + + def editObject(self, identifier, username=None, emailAddress=None, password=None): + """Edit email delivery account related details. + + :param int identifier: The ID of the email account + :param string username: username of the email account. + :param string email: email of the email account. + :param string password: password of the email account to be updated to. + """ + data = {} + if username: + data['username'] = username + if emailAddress: + data['emailAddress'] = emailAddress + if password: + data['password'] = password + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject', data, id=identifier) diff --git a/docs/cli.rst b/docs/cli.rst index a659b145c..264f5bcaf 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -79,6 +79,7 @@ To discover the available commands, simply type `slcli`. config CLI configuration. dedicatedhost Dedicated Host. dns Domain Name System. + email Email Deliviry Network event-log Event Logs. file File Storage. firewall Firewalls. diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 574fba1bc..732d524d7 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -6,4 +6,12 @@ Email Commands .. click:: SoftLayer.CLI.email.list:cli :prog: email list + :show-nested: + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: + +.. click:: SoftLayer.CLI.email.edit:cli + :prog: email edit :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index 798745a7f..f2c8aa693 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -9,7 +9,21 @@ class EmailCLITests(testing.TestCase): - def test_detail(self): + def test_list(self): result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') + + def test_detail(self): + result = self.run_command(['email', 'detail', '1232123']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_edit(self): + result = self.run_command(['email', 'edit', '1232123', + '--username=test@ibm.com', + '--email=test@ibm.com', + '--password=test123456789']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b8a2b58b6..fad965c7a 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -21,3 +21,9 @@ def test_get_statistics(self): self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') + + def test_get_object(self): + self.manager = EmailManager(self.client) + self.manager.get_instance(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject') From 1671c767ba4442d810d1278e03ac1df323bbe6ca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:24:36 -0500 Subject: [PATCH 0127/1050] #1486 fixed newly failing unit tests --- SoftLayer/CLI/virt/create.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 24 +++++++----------------- tests/api_tests.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 23a18457d..9d07f01d2 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -177,7 +177,7 @@ def _parse_create_args(client, args): help="Forces the VS to only have access the private network") @click.option('--like', is_eager=True, callback=_update_with_like_args, help="Use the configuration from an existing VS") -@click.option('--network', '-n', help="Network port speed in Mbps") +@click.option('--network', '-n', help="Network port speed in Mbps", type=click.INT) @helpers.multi_option('--tag', '-g', help="Tags to add to the instance") @click.option('--template', '-t', is_eager=True, callback=template.TemplateCallback(list_args=['disk', 'key', 'tag']), diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index e9bb2fd74..efcb8cbc3 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -42,7 +42,7 @@ def test_create(self, confirm_mock): 'hostname': 'host', 'startCpus': 2, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}], + 'networkComponents': [{'maxSpeed': 100}], 'supplementalCreateObjectOptions': {'bootMode': None}},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -225,7 +225,7 @@ def test_create_with_integer_image_guid(self, confirm_mock): 'supplementalCreateObjectOptions': {'bootMode': None}, 'blockDeviceTemplateGroup': {'globalIdentifier': 'aaaa1111bbbb2222'}, 'datacenter': {'name': 'dal05'}, - 'networkComponents': [{'maxSpeed': '100'}] + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -257,7 +257,7 @@ def test_create_with_flavor(self, confirm_mock): 'bootMode': None, 'flavorKeyName': 'B1_1X2X25'}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}]},) + 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -351,21 +351,11 @@ def test_create_with_host_id(self, confirm_mock): 'domain': 'example.com', 'localDiskFlag': True, 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': { - 'bootMode': None - }, - 'dedicatedHost': { - 'id': 123 - }, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'dedicatedHost': {'id': 123}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': { - 'name': 'dal05' - }, - 'networkComponents': [ - { - 'maxSpeed': '100' - } - ] + 'datacenter': {'name': 'dal05'}, + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=template_args) diff --git a/tests/api_tests.py b/tests/api_tests.py index a83246858..c3d84529a 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,6 +96,19 @@ def test_simple_call(self): offset=None, ) + + def test_simple_call_2(self): + mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') + mock.return_value = {"test": "result"} + + resp = self.client.call('SERVICE', 'METHOD', {'networkComponents': [{'maxSpeed': 100}]}) + + self.assertEqual(resp, {"test": "result"}) + self.assert_called_with('SoftLayer_SERVICE', 'METHOD', + mask=None, filter=None, identifier=None, + args=({'networkComponents': [{'maxSpeed': 100}]},), limit=None, offset=None, + ) + def test_verify_request_false(self): client = SoftLayer.BaseClient(transport=self.mocks) mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') From eff3bf2153368f01724d8e265496ac68ee419999 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:30:47 -0500 Subject: [PATCH 0128/1050] fixed tox --- tests/api_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api_tests.py b/tests/api_tests.py index c3d84529a..ea4726a6e 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,7 +96,6 @@ def test_simple_call(self): offset=None, ) - def test_simple_call_2(self): mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') mock.return_value = {"test": "result"} From 96889809e04f3aa4114c7e102fa27fef838e5364 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 17 May 2021 18:21:33 -0400 Subject: [PATCH 0129/1050] Add a table result for the hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 97 +++++++++++- SoftLayer/fixtures/SoftLayer_Product_Order.py | 148 ++++++++++++++++++ ...ftLayer_Provisioning_Maintenance_Window.py | 26 +++ SoftLayer/managers/hardware.py | 44 ++++-- tests/CLI/modules/server_tests.py | 49 +++--- tests/managers/hardware_tests.py | 10 +- 6 files changed, 335 insertions(+), 39 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 037506df0..89b89859e 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils import SoftLayer from SoftLayer.CLI import environment @@ -35,6 +36,9 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " @@ -57,7 +61,92 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} disk_list.append(disks) - if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, disk=disk_list, test=test): - raise exceptions.CLIAbort('Hardware Server Upgrade Failed') - env.fout('Successfully Upgraded.') + response = mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, disk=disk_list, test=test) + + if response: + if test: + add_data_to_table(response, table) + else: + table.add_row(['order_date', response.get('orderDate')]) + table.add_row(['order_id', response.get('orderId')]) + add_data_to_table(response['orderDetails'], table) + place_order_table = get_place_order_information(response) + table.add_row(['Place Order Information', place_order_table]) + order_detail_table = get_order_detail(response) + table.add_row(['Order Detail (Billing Information)', order_detail_table]) + + env.fout(table) + + +def add_data_to_table(response, table): + """Add the hardware server upgrade result to the table""" + table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) + table.add_row(['quantity', response.get('quantity')]) + table.add_row(['package_id', response.get('packageId')]) + table.add_row(['currency_short_name', response.get('currencyShortName')]) + table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) + table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) + table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table_hardware = get_hardware_detail(response) + table.add_row(['Hardware', table_hardware]) + table_prices = get_hardware_prices(response) + table.add_row(['prices', table_prices]) + + +def get_place_order_information(response): + """Get the hardware server place order information.""" + table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order.add_row([response.get('id'), + response.get('accountId'), + response.get('status'), + utils.lookup(response, 'account', 'companyName'), + utils.lookup(response, 'userRecord', 'firstName'), + utils.lookup(response, 'account', 'lastName'), + utils.lookup(response, 'account', 'username')]) + + return table_place_order + + +def get_hardware_detail(response): + """Get the hardware server detail.""" + table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) + for hardware in response['hardware']: + table_hardware.add_row([hardware.get('accountId'), + hardware.get('hostname'), + hardware.get('domain')]) + + return table_hardware + + +def get_hardware_prices(response): + """Get the hardware server prices.""" + table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + 'Item Units']) + for price in response['prices']: + categories = price.get('categories')[0] + table_prices.add_row([price.get('id'), + price.get('hourlyRecurringFee'), + price.get('recurringFee'), + categories.get('name'), + utils.lookup(price, 'item', 'description'), + utils.lookup(price, 'item', 'units')]) + + return table_prices + + +def get_order_detail(response): + """Get the hardware server order detail.""" + table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', + 'billing_name_first', 'billing_name_last', 'billing_postal_code', + 'billing_state']) + table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameFirst'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameLast'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingPostalCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingState')]) + + return table_order_detail diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..83604f8d1 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -63,6 +63,154 @@ 'postTaxRecurring': '0.32', } +hardware_verifyOrder = { + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111, + "domain": "testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "units": "GB" + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "sendQuoteEmailFlag": None, + "totalRecurringTax": "0", + "useHourlyPricing": False +} + +hardware_placeOrder = { + "orderDate": "2021-05-07T07:41:41-06:00", + "orderDetails": { + "billingInformation": { + "billingAddressLine1": "4849 Alpha Rd", + "billingCity": "Dallas", + "billingCountryCode": "US", + "billingEmail": "test.ibm.com", + "billingNameCompany": "SoftLayer Internal - Development Community", + "billingNameFirst": "Test", + "billingNameLast": "Test", + "billingPhoneVoice": "1111111", + "billingPostalCode": "75244-1111", + "billingState": "TX", + }, + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111111, + "bareMetalInstanceFlag": 0, + "domain": "testedit.com", + "fullyQualifiedDomainName": "bardcabero.testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-1111111" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "paymentType": "ADD_TO_BALANCE", + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "postTaxSetup": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "preTaxSetup": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG", + "units": "GB", + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "totalRecurringTax": "0", + "useHourlyPricing": False + }, + "orderId": 78332111, + "placedOrder": { + "accountId": 1111111, + "id": 1234, + "status": "PENDING_UPGRADE", + "account": { + "brandId": 2, + "companyName": "SoftLayer Internal - Development Community", + "id": 1234 + }, + "items": [ + { + "categoryCode": "ram", + "description": "32 GB RAM", + "id": 824199364, + "recurringFee": "0" + } + ], + "userRecord": { + "accountId": 1234, + "firstName": "test", + "id": 3333, + "lastName": "cabero", + "username": "sl1234-dcabero" + } + } +} + rsc_placeOrder = { 'orderDate': '2013-08-01 15:23:45', 'orderId': 1234, diff --git a/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py new file mode 100644 index 000000000..f3e597481 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py @@ -0,0 +1,26 @@ +getMaintenanceWindows = [ + { + "beginDate": "2021-05-13T16:00:00-06:00", + "dayOfWeek": 4, + "endDate": "2021-05-13T19:00:00-06:00", + "id": 198351, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDat": "2021-05-14T00:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T03:00:00-06:00", + "id": 189747, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDate": "2021-05-14T08:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T11:00:00-06:00", + "id": 198355, + "locationId": 1441195, + "portalTzId": 114 + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..4643adcb8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -834,6 +834,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ + result = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -849,14 +850,24 @@ def upgrade(self, instance_id, memory=None, server_response = self.get_instance(instance_id) package_id = server_response['billingItem']['package']['id'] + location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) + maintenance_window_id = self.get_maintenance_windows_id(location_id) + order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', - 'properties': [{ - 'name': 'MAINTENANCE_WINDOW', - 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") - }], + 'properties': [ + { + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }, + { + 'name': 'MAINTENANCE_WINDOW_ID', + 'value': str(maintenance_window_id) + } + + ], 'hardware': [{'id': int(instance_id)}], 'packageId': package_id } @@ -878,11 +889,26 @@ def upgrade(self, instance_id, memory=None, if prices: if test: - self.client['Product_Order'].verifyOrder(order) + result = self.client['Product_Order'].verifyOrder(order) else: - self.client['Product_Order'].placeOrder(order) - return True - return False + result = self.client['Product_Order'].placeOrder(order) + return result + + def get_maintenance_windows_id(self, location_id): + """Get the disks prices to be added or upgraded. + + :param int location_id: Hardware Server location id. + :return int. + """ + begin_date_object = datetime.datetime.now() + begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + end_date_object = datetime.date.today() + datetime.timedelta(days=30) + end_date = end_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + + result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, + end_date, + location_id) + return result_windows[0]['id'] @retry(logger=LOGGER) def get_instance(self, instance_id): @@ -893,7 +919,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName],nextInvoiceChildren]' + 'datacenter,billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..3a400842f 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -14,6 +14,7 @@ from unittest import mock as mock from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer import SoftLayerError from SoftLayer import testing @@ -691,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -746,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) @@ -936,9 +937,9 @@ def test_upgrade_aborted(self, confirm_mock): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.formatting.confirm') - def test_upgrade_test(self, confirm_mock): - confirm_mock.return_value = True + def test_upgrade_test(self): + order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_verifyOrder result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) @@ -946,6 +947,8 @@ def test_upgrade_test(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_add_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) self.assert_no_fail(result) @@ -953,6 +956,8 @@ def test_upgrade_add_disk(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_resize_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) self.assert_no_fail(result) @@ -981,6 +986,8 @@ def test_upgrade_disk_does_not_exist(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..c3dd67a4d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -879,7 +879,7 @@ def test_get_price_id_disk_capacity(self): def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -891,7 +891,7 @@ def test_upgrade_add_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -903,7 +903,7 @@ def test_upgrade_resize_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -912,14 +912,14 @@ def test_upgrade_resize_disk(self): def test_upgrade_blank(self): result = self.hardware.upgrade(1) - self.assertEqual(result, False) + self.assertEqual(result, None) self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) def test_upgrade_full(self): result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", public_bandwidth=500, test=False) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] From 6bd61d8d982a09b44359a71633db88b4f6431844 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 May 2021 09:19:38 -0400 Subject: [PATCH 0130/1050] fix team code review comments --- SoftLayer/CLI/email/detail.py | 11 ++++++-- SoftLayer/CLI/email/edit.py | 25 +++++++++++++------ SoftLayer/CLI/email/list.py | 12 ++++++--- ...Network_Message_Delivery_Email_Sendgrid.py | 1 + SoftLayer/managers/email.py | 14 ++++++++--- tests/CLI/modules/email_tests.py | 2 ++ tests/managers/email_tests.py | 6 +++++ 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 28fccba57..8d7ca18a5 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -1,7 +1,8 @@ -"""Display details for a specified email.""" +"""Display details for a specified email account.""" # :license: MIT, see LICENSE for more details. import click +from SoftLayer.CLI.email.list import build_statistics_table from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.email import EmailManager @@ -23,11 +24,17 @@ def cli(env, identifier): table.add_row(['id', result.get('id')]) table.add_row(['username', result.get('username')]) + table.add_row(['email_address', result.get('emailAddress')]) table.add_row(['create_date', result.get('createDate')]) - table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['category_code', utils.lookup(result, 'billingItem', 'categoryCode')]) table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + statistics = email_manager.get_statistics(identifier) + + for statistic in statistics: + table.add_row(['statistics', build_statistics_table(statistic)]) + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py index 321e6306f..16703b2aa 100644 --- a/SoftLayer/CLI/email/edit.py +++ b/SoftLayer/CLI/email/edit.py @@ -10,23 +10,32 @@ @click.command() @click.argument('identifier') -@click.option('--username', help="username account") -@click.option('--email', help="Additional note for the image") +@click.option('--username', help="Sets username for this account") +@click.option('--email', help="Sets the contact email for this account") @click.option('--password', help="Password must be between 8 and 20 characters " "and must contain one letter and one number.") @environment.pass_env def cli(env, identifier, username, email, password): """Edit details of an email delivery account.""" + email_manager = EmailManager(env.client) + data = {} + update = False + if email: + if email_manager.update_email(identifier, email): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit emailAddress account") if username: data['username'] = username - if email: - data['emailAddress'] = email if password: data['password'] = password + if len(data) != 0: + if email_manager.editObject(identifier, **data): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit email account") - email_manager = EmailManager(env.client) - - if not email_manager.editObject(identifier, data): - raise exceptions.CLIAbort("Failed to Edit email account") + if update: + env.fout('Updated Successfully') diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index a5353a31a..87912b37f 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -36,25 +36,29 @@ def cli(env): table.add_row(['email_information', table_information]) table.add_row(['email_overview', overview_table]) for statistic in statistics: - table.add_row(['statistics', _build_statistics_table(statistic)]) + table.add_row(['statistics', build_statistics_table(statistic)]) env.fout(table) def _build_overview_table(email_overview): - table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) + table = formatting.Table( + ['credit_allowed', 'credits_remain', 'credits_overage', 'credits_used', + 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('creditsOverage'), email_overview.get('creditsUsed'), email_overview.get('package'), email_overview.get('reputation'), email_overview.get('requests')]) return table -def _build_statistics_table(statistics): - table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) +def build_statistics_table(statistics): + """statistics records of Email Delivery account""" + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spam_reports']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 9bff8ad08..6a64d1be6 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -56,3 +56,4 @@ } editObject = True +updateEmailAddress = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index df3c144e5..57af17ce7 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -57,7 +57,7 @@ def get_instance(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject', id=identifier, mask=_mask) - def editObject(self, identifier, username=None, emailAddress=None, password=None): + def editObject(self, identifier, username=None, password=None): """Edit email delivery account related details. :param int identifier: The ID of the email account @@ -68,10 +68,18 @@ def editObject(self, identifier, username=None, emailAddress=None, password=None data = {} if username: data['username'] = username - if emailAddress: - data['emailAddress'] = emailAddress if password: data['password'] = password return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject', data, id=identifier) + + def update_email(self, identifier, email): + """Edit email address delivery account . + + :param int identifier: The ID of the email account + :param string email: email of the email account. + + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress', email, id=identifier) diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index f2c8aa693..0e2b398d6 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -27,3 +27,5 @@ def test_edit(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index fad965c7a..94ea84b52 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -27,3 +27,9 @@ def test_get_object(self): self.manager.get_instance(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_update_email_address(self): + self.manager = EmailManager(self.client) + self.manager.update_email(1232123, 'test@ibm.com') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') From d92a1723f77eacc1f91707ffc4019b4ce1e1af77 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 24 May 2021 16:22:42 -0500 Subject: [PATCH 0131/1050] changed a testing domain to one that really doesnt exist --- SoftLayer/transports.py | 7 +++--- tests/transport_tests.py | 49 ++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index d0afe3d3b..bd643b568 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -395,7 +395,8 @@ def __call__(self, request): try: result = json.loads(resp.text) except ValueError as json_ex: - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) else: raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") @@ -413,8 +414,8 @@ def __call__(self, request): except ValueError as json_ex: if ex.response.text == "": raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) raise exceptions.SoftLayerAPIError(ex.response.status_code, message) except requests.RequestException as ex: diff --git a/tests/transport_tests.py b/tests/transport_tests.py index ca3dcc73b..c23f1054f 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -40,7 +40,7 @@ class TestXmlRpcAPICall(testing.TestCase): def set_up(self): self.transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) self.response = get_xmlrpc_response() @@ -71,7 +71,7 @@ def test_call(self, request): resp = self.transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -123,7 +123,7 @@ def test_identifier(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.identifier = 1234 @@ -141,7 +141,7 @@ def test_filter(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} @@ -159,7 +159,7 @@ def test_limit_offset(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.limit = 10 @@ -179,7 +179,7 @@ def test_old_mask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = {"something": "nested"} @@ -201,7 +201,7 @@ def test_mask_call_no_mask_prefix(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "something.nested" @@ -217,7 +217,7 @@ def test_mask_call_v2(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask[something[nested]]" @@ -233,7 +233,7 @@ def test_mask_call_filteredMask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "filteredMask[something[nested]]" @@ -249,7 +249,7 @@ def test_mask_call_v2_dot(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask.something.nested" @@ -313,7 +313,7 @@ def test_ibm_id_call(self, auth, request): auth.assert_called_with('apikey', '1234567890qweasdzxc') request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -385,7 +385,7 @@ def test_verify(request, request.return_value = get_xmlrpc_response() transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) req = transports.Request() @@ -401,7 +401,7 @@ def test_verify(request, transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', data=mock.ANY, headers=mock.ANY, cert=mock.ANY, @@ -415,7 +415,7 @@ class TestRestAPICall(testing.TestCase): def set_up(self): self.transport = transports.RestTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -435,7 +435,7 @@ def test_basic(self, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', headers=mock.ANY, auth=None, data=None, @@ -501,7 +501,6 @@ def test_proxy_without_protocol(self): req.service = 'SoftLayer_Service' req.method = 'Resource' req.proxy = 'localhost:3128' - try: self.assertRaises(SoftLayer.TransportError, self.transport, req) except AssertionError: @@ -519,7 +518,7 @@ def test_valid_proxy(self, request): self.transport(req) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, auth=None, @@ -544,7 +543,7 @@ def test_with_id(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -568,7 +567,7 @@ def test_with_args(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", 1]}', @@ -592,7 +591,7 @@ def test_with_args_bytes(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", "YXNkZg=="]}', @@ -616,7 +615,7 @@ def test_with_filter(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectFilter': '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, headers=mock.ANY, @@ -641,7 +640,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -662,7 +661,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -688,7 +687,7 @@ def test_with_limit_offset(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -731,7 +730,7 @@ def test_with_special_auth(self, auth, request): auth.assert_called_with(user, password) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=mock.ANY, data=None, From 2a8637ac677498ecd728aaf1fcc3717387425951 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 24 May 2021 20:44:48 -0400 Subject: [PATCH 0132/1050] fix team code review --- SoftLayer/CLI/email/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 87912b37f..d2042b350 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get lists Email Delivery account Service """ +"""Lists Email Delivery Service """ # :license: MIT, see LICENSE for more details. import click From 668035f02f2f477bfecd5eac59526d7dee710e59 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 16:48:20 -0500 Subject: [PATCH 0133/1050] v5.9.5 updates --- CHANGELOG.md | 14 ++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55340957..4e70595fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log + +## [5.9.4] - 2021-04-24 +https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 + +#### Improvements +- Changed a testing domain to one that really doesnt exist #1492 +- Fix Incomplete notes field for file and block #1484 +- Show component versions on hw detail #1470 +- Add the firewall information on slcli firewall detail #1475 +- Add an --orderBy parameters to call-api #1459 +- Add image detail transaction data #1479 + + + ## [5.9.4] - 2021-04-27 https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cec2831e0..26a00c4cd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.4' +VERSION = 'v5.9.5' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 48c1e4e6c..c2e70f61a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.4', + version='5.9.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 66a0908452f03c2980b93db04b34fba1eeebb356 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 21:05:39 -0500 Subject: [PATCH 0134/1050] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e70595fa..0a79b6d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [5.9.4] - 2021-04-24 +## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 #### Improvements From a88f14761caf134d374f01d2e2b08bd4aec0143c Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:24:22 -0500 Subject: [PATCH 0135/1050] Update snapcraft.yaml --- snap/snapcraft.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a318890e6..c4a0daeeb 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,7 +6,7 @@ description: | license: MIT -base: core18 +base: core20 grade: stable confinement: strict @@ -25,7 +25,6 @@ parts: source: https://github.com/softlayer/softlayer-python source-type: git plugin: python - python-version: python3 override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" @@ -34,4 +33,4 @@ parts: - python3 stage-packages: - - python3 \ No newline at end of file + - python3 From 8fd0e86c91f9a6aacf58db9712eacbef7abf1ceb Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:28:04 -0500 Subject: [PATCH 0136/1050] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c4a0daeeb..f8e2e2267 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,7 +12,7 @@ confinement: strict apps: slcli: - command: slcli + command: bin/slcli environment: LC_ALL: C.UTF-8 plugs: From 79a06c38bb48bb4d9712fec2d50ec26a7b2e2d72 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:34:47 -0500 Subject: [PATCH 0137/1050] Update README.rst --- README.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2ae928347..fc05a3503 100644 --- a/README.rst +++ b/README.rst @@ -60,7 +60,12 @@ To install the slcli snap: .. code-block:: bash - $ sudo snap install slcli + $ sudo snap install slcli + + (or to get the latest release) + + $ sudo snap install slcli --edge + From b97540bf0eb7bbd9c16d3a745cbf3a5b7d9aa238 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 27 May 2021 17:32:58 -0400 Subject: [PATCH 0138/1050] slcli vlan cancel should report if a vlan is automatic --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/cancel.py | 30 ++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 12 +++++++- SoftLayer/managers/billing.py | 27 ++++++++++++++++++ SoftLayer/managers/network.py | 8 ++++++ docs/api/managers/billing.rst | 5 ++++ docs/cli/vlan.rst | 4 +++ tests/CLI/modules/vlan_tests.py | 12 ++++++++ 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/vlan/cancel.py create mode 100644 SoftLayer/managers/billing.py create mode 100644 docs/api/managers/billing.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..6672477ed 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -336,6 +336,7 @@ ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), + ('vlan:cancel', 'SoftLayer.CLI.vlan.cancel:cli'), ('summary', 'SoftLayer.CLI.summary:cli'), diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py new file mode 100644 index 000000000..7bef5e458 --- /dev/null +++ b/SoftLayer/CLI/vlan/cancel.py @@ -0,0 +1,30 @@ +"""Cancel Network Vlan.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers.billing import BillingManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel network vlan.""" + + mgr = SoftLayer.NetworkManager(env.client) + billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): + raise exceptions.CLIAbort('Aborted') + + item = mgr.get_vlan(identifier).get('billingItem') + if item: + billing.cancel_item(item.get('id'), 'cancel by cli command') + env.fout('Cancel Successfully') + else: + res = mgr.get_cancel_failure_reasons(identifier) + raise exceptions.ArgumentError(res) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 758fe3b39..4e1c100ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -5,9 +5,19 @@ }, 'id': 1234, 'vlanNumber': 4444, - 'firewallInterfaces': None + 'firewallInterfaces': None, + 'billingItem': { + 'allowCancellationFlag': 1, + 'categoryCode': 'network_vlan', + 'description': 'Private Network Vlan', + 'id': 235689, + 'notes': 'test cli', + 'orderItemId': 147258, + } } editObject = True setTags = True getList = [getObject] + +cancel = True diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py new file mode 100644 index 000000000..bb2c11e23 --- /dev/null +++ b/SoftLayer/managers/billing.py @@ -0,0 +1,27 @@ +""" + SoftLayer.BillingItem + ~~~~~~~~~~~~~~~~~~~ + BillingItem manager + + :license: MIT, see LICENSE for more details. +""" + + +class BillingManager(object): + """Manager for interacting with Billing item instances.""" + + def __init__(self, client): + self.client = client + + def cancel_item(self, identifier, reason_cancel): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + True, + reason_cancel, + reason_cancel, + id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d609de5d5..50429cbb1 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -49,6 +49,7 @@ 'primaryRouter[id, fullyQualifiedDomainName, datacenter]', 'totalPrimaryIpAddressCount', 'networkSpace', + 'billingItem', 'hardware', 'subnets', 'virtualGuests', @@ -752,3 +753,10 @@ def set_subnet_ipddress_note(self, identifier, note): """ result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result + + def get_cancel_failure_reasons(self, identifier): + """get the reasons by cannot cancel the VLAN + + :param integer identifier: the instance ID + """ + return self.vlan.getCancelFailureReasons(id=identifier) diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst new file mode 100644 index 000000000..f44a9333c --- /dev/null +++ b/docs/api/managers/billing.rst @@ -0,0 +1,5 @@ +.. _billing: + +.. automodule:: SoftLayer.managers.billing + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..57a58932a 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -14,3 +14,7 @@ VLANs .. click:: SoftLayer.CLI.vlan.list:cli :prog: vlan list :show-nested: + +.. click:: SoftLayer.CLI.vlan.cancel:cli + :prog: vlan cancel + :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..59de39ad3 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -99,3 +99,15 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_fail(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) From 03583ee7adb275d211a7c8a137d64b153c21186f Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 20:42:31 -0400 Subject: [PATCH 0139/1050] Refactor hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 32 +++++++++---------- SoftLayer/fixtures/SoftLayer_Product_Order.py | 10 +++--- SoftLayer/managers/hardware.py | 3 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 89b89859e..d9e0c9487 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -68,8 +68,8 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad if test: add_data_to_table(response, table) else: - table.add_row(['order_date', response.get('orderDate')]) - table.add_row(['order_id', response.get('orderId')]) + table.add_row(['Order Date', response.get('orderDate')]) + table.add_row(['Order Id', response.get('orderId')]) add_data_to_table(response['orderDetails'], table) place_order_table = get_place_order_information(response) table.add_row(['Place Order Information', place_order_table]) @@ -83,21 +83,21 @@ def add_data_to_table(response, table): """Add the hardware server upgrade result to the table""" table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) table.add_row(['quantity', response.get('quantity')]) - table.add_row(['package_id', response.get('packageId')]) - table.add_row(['currency_short_name', response.get('currencyShortName')]) - table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) - table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) - table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table.add_row(['Package Id', response.get('packageId')]) + table.add_row(['Currency Short Name', response.get('currencyShortName')]) + table.add_row(['Prorated Initial Charge', response.get('proratedInitialCharge')]) + table.add_row(['Prorated Order Total', response.get('proratedOrderTotal')]) + table.add_row(['Hourly Pricing', response.get('useHourlyPricing')]) table_hardware = get_hardware_detail(response) table.add_row(['Hardware', table_hardware]) table_prices = get_hardware_prices(response) - table.add_row(['prices', table_prices]) + table.add_row(['Prices', table_prices]) def get_place_order_information(response): """Get the hardware server place order information.""" - table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', - 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order = formatting.Table(['Id', 'Account Id', 'Status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord LastName', 'UserRecord Username']) table_place_order.add_row([response.get('id'), response.get('accountId'), response.get('status'), @@ -111,8 +111,8 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" - table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) - for hardware in response['hardware']: + table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) + for hardware in response['Hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) @@ -122,7 +122,7 @@ def get_hardware_detail(response): def get_hardware_prices(response): """Get the hardware server prices.""" - table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + table_prices = formatting.Table(['Id', 'HourlyRecurringFee', 'RecurringFee', 'Categories', 'Item Description', 'Item Units']) for price in response['prices']: categories = price.get('categories')[0] @@ -138,9 +138,9 @@ def get_hardware_prices(response): def get_order_detail(response): """Get the hardware server order detail.""" - table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', - 'billing_name_first', 'billing_name_last', 'billing_postal_code', - 'billing_state']) + table_order_detail = formatting.Table(['Billing City', 'Billing Country Code', 'Billing Email', + 'Billing Name First', 'Billing Name Last', 'Billing Postal Code', + 'Billing State']) table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 83604f8d1..be702ccba 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -69,7 +69,7 @@ { "accountId": 1111, "domain": "testedit.com", - "hostname": "bardcabero", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" } ], @@ -135,8 +135,8 @@ "accountId": 1111111, "bareMetalInstanceFlag": 0, "domain": "testedit.com", - "fullyQualifiedDomainName": "bardcabero.testedit.com", - "hostname": "bardcabero", + "fullyQualifiedDomainName": "test.testedit.com", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-1111111" } ], @@ -205,8 +205,8 @@ "accountId": 1234, "firstName": "test", "id": 3333, - "lastName": "cabero", - "username": "sl1234-dcabero" + "lastName": "test", + "username": "sl1234-test" } } } diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fd14549ce..5bfd50869 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -908,7 +908,8 @@ def get_maintenance_windows_id(self, location_id): result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, end_date, location_id) - return result_windows[0]['id'] + if len(result_windows) > 0: + return result_windows[0].get('id') @retry(logger=LOGGER) def get_instance(self, instance_id): From 0ad4846fa9a781d60efe923c22f6553c870f55b3 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 21:05:38 -0400 Subject: [PATCH 0140/1050] Fix unit test and tox analysis. --- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/managers/hardware.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d9e0c9487..a5b432e82 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -112,7 +112,7 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) - for hardware in response['Hardware']: + for hardware in response['hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5bfd50869..d7e65694e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -835,6 +835,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ result = None + maintenance_window_id = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -853,7 +854,9 @@ def upgrade(self, instance_id, memory=None, location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) - maintenance_window_id = self.get_maintenance_windows_id(location_id) + maintenance_window_detail = self.get_maintenance_windows_detail(location_id) + if maintenance_window_detail: + maintenance_window_id = maintenance_window_detail.get('id') order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', @@ -894,12 +897,13 @@ def upgrade(self, instance_id, memory=None, result = self.client['Product_Order'].placeOrder(order) return result - def get_maintenance_windows_id(self, location_id): + def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. :return int. """ + result = None begin_date_object = datetime.datetime.now() begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") end_date_object = datetime.date.today() + datetime.timedelta(days=30) @@ -909,7 +913,9 @@ def get_maintenance_windows_id(self, location_id): end_date, location_id) if len(result_windows) > 0: - return result_windows[0].get('id') + result = result_windows[0] + + return result @retry(logger=LOGGER) def get_instance(self, instance_id): From 773f4d59d37407566c8d446dbcc1bbb2b64f6a35 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 1 Jun 2021 11:25:16 -0400 Subject: [PATCH 0141/1050] Remove block/file interval option for replica volume. --- SoftLayer/CLI/block/replication/order.py | 15 +++++++++------ SoftLayer/CLI/file/replication/order.py | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/block/replication/order.py b/SoftLayer/CLI/block/replication/order.py index 743c91c0e..5324dfc19 100644 --- a/SoftLayer/CLI/block/replication/order.py +++ b/SoftLayer/CLI/block/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -40,13 +42,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): """Order a block storage replica volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') if tier is not None: tier = float(tier) try: order = block_manager.order_replicant_volume( - volume_id, + block_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -57,9 +60,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") diff --git a/SoftLayer/CLI/file/replication/order.py b/SoftLayer/CLI/file/replication/order.py index 9ba2c84f8..2961cbd64 100644 --- a/SoftLayer/CLI/file/replication/order.py +++ b/SoftLayer/CLI/file/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -29,13 +31,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier): """Order a file storage replica volume.""" file_manager = SoftLayer.FileStorageManager(env.client) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') if tier is not None: tier = float(tier) try: order = file_manager.order_replicant_volume( - volume_id, + file_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -45,9 +48,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") From 7d2de3c5d37f29a89b2d40ec4f66c3cc3cbf64ac Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 3 Jun 2021 12:35:56 -0400 Subject: [PATCH 0142/1050] add new feature on vlan cli --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/create.py | 40 ++++++++++++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 16 +++++ .../fixtures/SoftLayer_Product_Package.py | 63 +++++++++++++++++++ docs/cli/vlan.rst | 4 ++ tests/CLI/modules/vlan_tests.py | 21 +++++++ 6 files changed, 145 insertions(+) create mode 100644 SoftLayer/CLI/vlan/create.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..5eb3a006b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -333,6 +333,7 @@ ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), ('vlan', 'SoftLayer.CLI.vlan'), + ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py new file mode 100644 index 000000000..6d4a85273 --- /dev/null +++ b/SoftLayer/CLI/vlan/create.py @@ -0,0 +1,40 @@ +"""Order/create a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(epilog="See 'slcli server create-options' for valid options.") +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), + help='Network vlan type') +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), + help="Billing rate") +@environment.pass_env +def cli(env, hostname, datacenter, network, billing): + """Order/create a vlan instance.""" + + item_package = ['PUBLIC_NETWORK_VLAN'] + complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + if not network: + item_package = ['PRIVATE_NETWORK_VLAN'] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='NETWORK_VLAN', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=billing, + extras={'name': hostname}) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..df9747173 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -104,3 +104,19 @@ ] } } + +vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [{ + "id": 2018, + "itemId": 1071, + "categories": [{ + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}], + "item": { + "capacity": "0", + "description": "Public Network Vlan", + "id": 1071, + "keyName": "PUBLIC_NETWORK_VLAN"}} + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..54c7880c9 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,66 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItemsVLAN = [{ + "description": "Private Network Vlan", + "id": 1072, + "itemTaxCategoryId": 166, + "keyName": "PRIVATE_NETWORK_VLAN", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}, + "prices": [{ + "id": 203707, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 505, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203727, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +}, { + "description": "Public Network Vlan", + "id": 1071, + "itemTaxCategoryId": 166, + "keyName": "PUBLIC_NETWORK_VLAN", + "units": "N/A", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan", + }, + "prices": [{ + "id": 203637, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 509, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203667, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +} +] diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..b68f3d26e 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -3,6 +3,10 @@ VLANs ===== +.. click:: SoftLayer.CLI.vlan.create:cli + :prog: vlan create + :show-nested: + .. click:: SoftLayer.CLI.vlan.detail:cli :prog: vlan detail :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..5a1e7d4ae 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,8 +4,11 @@ :license: MIT, see LICENSE for more details. """ +import json from unittest import mock as mock +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing @@ -99,3 +102,21 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + def test_create_vlan(self): + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '-H test', + '-d TEST00', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) From 47d14d0918cfe16cc6c99d088b664c9b65fd46c4 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Jun 2021 10:49:25 -0400 Subject: [PATCH 0143/1050] fix team code review comments --- SoftLayer/CLI/vlan/cancel.py | 17 +++++++----- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 5 ++++ SoftLayer/managers/billing.py | 27 -------------------- SoftLayer/managers/network.py | 16 +++++++++++- tests/CLI/modules/vlan_tests.py | 8 ++++++ 5 files changed, 39 insertions(+), 34 deletions(-) delete mode 100644 SoftLayer/managers/billing.py diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 7bef5e458..79647a7eb 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -7,7 +7,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer.managers.billing import BillingManager @click.command() @@ -17,14 +16,20 @@ def cli(env, identifier): """Cancel network vlan.""" mgr = SoftLayer.NetworkManager(env.client) - billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): raise exceptions.CLIAbort('Aborted') + reasons = mgr.get_cancel_failure_reasons(identifier) + if len(reasons) > 0: + raise exceptions.CLIAbort(reasons) item = mgr.get_vlan(identifier).get('billingItem') if item: - billing.cancel_item(item.get('id'), 'cancel by cli command') - env.fout('Cancel Successfully') + mgr.cancel_item(item.get('id'), + True, + 'Cancel by cli command', + 'Cancel by cli command') else: - res = mgr.get_cancel_failure_reasons(identifier) - raise exceptions.ArgumentError(res) + raise exceptions.CLIAbort( + "VLAN is an automatically assigned and free of charge VLAN," + " it will automatically be removed from your account when it is empty") diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 4e1c100ad..960c98995 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -21,3 +21,8 @@ getList = [getObject] cancel = True + +getCancelFailureReasons = [ + "1 bare metal server(s) still on the VLAN ", + "1 virtual guest(s) still on the VLAN " +] diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py deleted file mode 100644 index bb2c11e23..000000000 --- a/SoftLayer/managers/billing.py +++ /dev/null @@ -1,27 +0,0 @@ -""" - SoftLayer.BillingItem - ~~~~~~~~~~~~~~~~~~~ - BillingItem manager - - :license: MIT, see LICENSE for more details. -""" - - -class BillingManager(object): - """Manager for interacting with Billing item instances.""" - - def __init__(self, client): - self.client = client - - def cancel_item(self, identifier, reason_cancel): - """Cancel a billing item immediately, deleting all its data. - - :param integer identifier: the instance ID to cancel - :param string reason_cancel: reason cancel - """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - True, - True, - reason_cancel, - reason_cancel, - id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 50429cbb1..7c86dda58 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -755,8 +755,22 @@ def set_subnet_ipddress_note(self, identifier, note): return result def get_cancel_failure_reasons(self, identifier): - """get the reasons by cannot cancel the VLAN + """get the reasons why we cannot cancel the VLAN. :param integer identifier: the instance ID """ return self.vlan.getCancelFailureReasons(id=identifier) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + cancel_immediately, + reason_cancel, + customer_note, + id=identifier) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 59de39ad3..af2b22290 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -103,9 +103,17 @@ def test_vlan_list(self): @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True + mock = self.set_mock('SoftLayer_Network_Vlan', 'getCancelFailureReasons') + mock.return_value = [] result = self.run_command(['vlan', 'cancel', '1234']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_error(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel_fail(self, confirm_mock): confirm_mock.return_value = False From 643bae8b3239d60fea786043d0722e003276f107 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 10:57:38 -0400 Subject: [PATCH 0144/1050] Add slcli account licenses --- SoftLayer/CLI/account/licenses.py | 41 +++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 107 ++++++++++++++++++++++++ SoftLayer/managers/account.py | 22 +++++ tests/CLI/modules/account_tests.py | 6 ++ tests/managers/account_tests.py | 8 ++ 6 files changed, 185 insertions(+) create mode 100644 SoftLayer/CLI/account/licenses.py diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py new file mode 100644 index 000000000..cdf6e177f --- /dev/null +++ b/SoftLayer/CLI/account/licenses.py @@ -0,0 +1,41 @@ +"""Show the all account licenses.""" +# :license: MIT, see LICENSE for more details. +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager + + +@click.command() +@environment.pass_env +def cli(env): + """return the control panel and VMWare licenses""" + + manager = AccountManager(env.client) + + panel_control = manager.get_active_virtual_licenses() + vmwares = manager.get_active_account_licenses() + + table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', + 'key', 'subnet', 'subnet notes']) + + table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', + 'manufacturer', 'requiredUser']) + for panel in panel_control: + table_panel.add_row([panel.get('id'), panel.get('ipAddress'), + utils.lookup(panel, 'softwareDescription', 'manufacturer'), + utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), + panel.get('key'), utils.lookup(panel, 'subnet', 'broadcastAddress'), + utils.lookup(panel, 'subnet', 'note')]) + + env.fout(table_panel) + for vmware in vmwares: + table_vmware.add_row([utils.lookup(vmware, 'softwareDescription', 'name'), + vmware.get('key'), vmware.get('capacity'), + utils.lookup(vmware, 'billingItem', 'description'), + utils.lookup(vmware, 'softwareDescription', 'manufacturer'), + utils.lookup(vmware, 'softwareDescription', 'requiredUser')]) + + env.fout(table_vmware) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..4a267b7a4 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -16,6 +16,7 @@ ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), ('account:events', 'SoftLayer.CLI.account.events:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), + ('account:licenses', 'SoftLayer.CLI.account.licenses:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4cfafa30f..11d1b26d0 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1100,3 +1100,110 @@ "smtpAccess": "1" } ] + +getActiveAccountLicenses = [{ + "accountId": 123456, + "capacity": "4", + "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 741258963, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 963258741, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 15963, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } +}, + { + "accountId": 123456, + "capacity": "4", + "key": "4122M-ABXC05-K829T-098HP-00QJM", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "description": "vCenter Server Appliance 6.x", + "id": 36987456, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 25839, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 1472, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } + } +] + +getActiveVirtualLicenses = [{ + "id": 12345, + "ipAddress": "192.168.23.78", + "key": "PLSK.06866259.0000", + "billingItem": { + "categoryCode": "control_panel", + "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " + }, + "softwareDescription": { + "longDescription": "Plesk - Unlimited Domain w/ Power Pack for VPS 17.8.11 Linux", + "manufacturer": "Plesk", + "name": "Plesk - Unlimited Domain w/ Power Pack for VPS" + }, + "subnet": { + "broadcastAddress": "192.168.23.79", + "cidr": 28, + "gateway": "192.168.23.65", + "id": 1973163, + "isCustomerOwned": False, + "isCustomerRoutable": False, + "netmask": "255.255.255.240", + "networkIdentifier": "128.116.23.64", + "networkVlanId": 123456, + "note": "test note", + "sortOrder": "1", + "subnetType": "ADDITIONAL_PRIMARY", + "totalIpAddresses": "16", + "usableIpAddressCount": "13", + "version": 4 + } +}] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index b7398076d..841f2e92d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -303,3 +303,25 @@ def get_network_message_delivery_accounts(self): _mask = """vendor,type""" return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) + + def get_active_virtual_licenses(self): + """Gets all active virtual licenses account. + + :returns: active virtual licenses account + """ + + _mask = """billingItem[categoryCode,createDate,description], + key,id,ipAddress, + softwareDescription[longDescription,name,manufacturer], + subnet""" + + return self.client['SoftLayer_Account'].getActiveVirtualLicenses(mask=_mask) + + def get_active_account_licenses(self): + """Gets all active account licenses. + + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" + + return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 9b88e3fae..fe9c11b0b 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -116,3 +116,9 @@ def test_acccount_order(self): result = self.run_command(['account', 'orders']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order', 'getAllObjects') + + def test_acccount_licenses(self): + result = self.run_command(['account', 'licenses']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') + self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index c5d2edf95..26b5dadff 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -153,3 +153,11 @@ def test_get_item_details_with_invoice_item_id(self): def test_get_routers(self): self.manager.get_routers() self.assert_called_with("SoftLayer_Account", "getRouters") + + def test_get_active_account_licenses(self): + self.manager.get_active_account_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveAccountLicenses") + + def test_get_active_virtual_licenses(self): + self.manager.get_active_virtual_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") From d3e37906092e1351a0a629a3c95edcc29c9fd0eb Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 11:01:08 -0400 Subject: [PATCH 0145/1050] add documentation --- docs/cli/account.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 27b4198d5..719c44fde 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -38,4 +38,8 @@ Account Commands .. click:: SoftLayer.CLI.account.orders:cli :prog: account orders - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.account.licenses:cli + :prog: account licenses + :show-nested: From 0b0e2ef76dc5329314bf888a5b7e8a8f978e487a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 11:14:51 -0400 Subject: [PATCH 0146/1050] fix the last code review comments --- docs/api/managers/billing.rst | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/api/managers/billing.rst diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst deleted file mode 100644 index f44a9333c..000000000 --- a/docs/api/managers/billing.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _billing: - -.. automodule:: SoftLayer.managers.billing - :members: - :inherited-members: \ No newline at end of file From bee41a23aead3ab4a46e5ce7dc46a85ef4368dab Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 14:24:21 -0400 Subject: [PATCH 0147/1050] fix the last code review comments --- SoftLayer/CLI/vlan/cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 79647a7eb..35f5aa0f9 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -13,7 +13,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Cancel network vlan.""" + """Cancel network VLAN.""" mgr = SoftLayer.NetworkManager(env.client) From b62006f80e5d13e3598f0aa9a8fcdb402bbd961b Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 9 Jun 2021 15:19:53 -0400 Subject: [PATCH 0148/1050] Add the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 76 ++++++++++++++++ SoftLayer/CLI/routes.py | 1 + ...rk_CdnMarketplace_Configuration_Mapping.py | 21 +++++ SoftLayer/managers/cdn.py | 91 ++++++++++++++++++- docs/cli/cdn.rst | 4 + tests/CLI/modules/cdn_tests.py | 28 ++++++ tests/managers/cdn_tests.py | 36 ++++++++ 7 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/cdn/edit.py diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py new file mode 100644 index 000000000..c6cb052cd --- /dev/null +++ b/SoftLayer/CLI/cdn/edit.py @@ -0,0 +1,76 @@ +"""Edit a CDN Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('hostname') +@click.option('--header', '-H', + type=click.STRING, + help="Host header." + ) +@click.option('--http-port', '-t', + type=click.INT, + help="HTTP port." + ) +@click.option('--origin', '-o', + required=True, + type=click.STRING, + help="Origin server address." + ) +@click.option('--respect-headers', '-r', + type=click.Choice(['1', '0']), + help="Respect headers. The value 1 is On and 0 is Off." + ) +@click.option('--cache', '-c', multiple=True, type=str, + help="Cache key optimization. These are the valid options to choose: 'include-all', 'ignore-all', " + "'include-specified', 'ignore-specified'. If you select 'include-specified' or 'ignore-specified' " + "please add a description too using again --cache, " + "e.g --cache=include-specified --cache=description." + ) +@click.option('--performance-configuration', '-p', + type=click.Choice(['General web delivery', 'Large file optimization', 'Video on demand optimization']), + help="Optimize for, General web delivery', 'Large file optimization', 'Video on demand optimization', " + "the Dynamic content acceleration option is not added because this has a special configuration." + ) +@environment.pass_env +def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account.""" + + manager = SoftLayer.CDNManager(env.client) + + cache_result = {} + if cache: + if len(cache) > 1: + cache_result['cacheKeyQueryRule'] = cache[0] + cache_result['description'] = cache[1] + else: + cache_result['cacheKeyQueryRule'] = cache[0] + + cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + respect_headers=respect_headers, cache=cache_result, + performance_configuration=performance_configuration) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + for cdn in cdn_result: + table.add_row(['Create Date', cdn.get('createDate')]) + table.add_row(['Header', cdn.get('header')]) + table.add_row(['Http Port', cdn.get('httpPort')]) + table.add_row(['Origin Type', cdn.get('originType')]) + table.add_row(['Performance Configuration', cdn.get('performanceConfiguration')]) + table.add_row(['Protocol', cdn.get('protocol')]) + table.add_row(['Respect Headers', cdn.get('respectHeaders')]) + table.add_row(['Unique Id', cdn.get('uniqueId')]) + table.add_row(['Vendor Name', cdn.get('vendorName')]) + table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['cname', cdn.get('cname')]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..05bbc997c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -61,6 +61,7 @@ ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), + ('cdn:edit', 'SoftLayer.CLI.cdn.edit:cli'), ('cdn:list', 'SoftLayer.CLI.cdn.list:cli'), ('cdn:origin-add', 'SoftLayer.CLI.cdn.origin_add:cli'), ('cdn:origin-list', 'SoftLayer.CLI.cdn.origin_list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py index 51950b919..dc3ca1789 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py @@ -1,6 +1,8 @@ listDomainMappings = [ { + "cacheKeyQueryRule": "include-all", "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "createDate": "2020-09-29T15:19:01-06:00", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -17,6 +19,7 @@ listDomainMappingByUniqueId = [ { "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "performanceConfiguration": "Large file optimization", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -29,3 +32,21 @@ "vendorName": "akamai" } ] + +updateDomainMapping = [ + { + "createDate": "2021-02-09T19:32:29-06:00", + "originType": "HOST_SERVER", + "path": "/*", + "performanceConfiguration": "Large file optimization", + "protocol": "HTTP", + "respectHeaders": True, + "uniqueId": "424406419091111", + "vendorName": "akamai", + "header": "www.test.com", + "httpPort": 83, + "cname": "cdn.test.cloud", + "originHost": "1.1.1.1", + "cacheKeyQueryRule": "include: test" + } +] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 2ead8c1fc..42acee390 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ - +import SoftLayer from SoftLayer import utils @@ -170,3 +170,92 @@ def start_data(self): def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date + + def edit(self, hostname, header=None, http_port=None, origin=None, + respect_headers=None, cache=None, performance_configuration=None): + """Edit the cdn object. + + :param string hostname: The CDN hostname. + :param header: The cdn Host header. + :param http_port: The cdn HTTP port. + :param origin: The cdn Origin server address. + :param respect_headers: The cdn Respect headers. + :param cache: The cdn Cache key optimization. + :param performance_configuration: The cdn performance configuration. + + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) + if cdn_instance_detail is None: + raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) + + unique_id = cdn_instance_detail.get('uniqueId') + + config = { + 'uniqueId': unique_id, + 'originType': cdn_instance_detail.get('originType'), + 'protocol': cdn_instance_detail.get('protocol'), + 'path': cdn_instance_detail.get('path'), + 'vendorName': cdn_instance_detail.get('vendorName'), + 'cname': cdn_instance_detail.get('cname'), + 'domain': cdn_instance_detail.get('domain'), + 'httpPort': cdn_instance_detail.get('httpPort') + } + + if header: + config['header'] = header + + if http_port: + config['httpPort'] = http_port + + if origin: + config['origin'] = origin + + if respect_headers: + config['respectHeaders'] = respect_headers + + if cache: + if 'include-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('include', cache) + config['cacheKeyQueryRule'] = cache_key_rule + elif 'ignore-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('ignore', cache) + config['cacheKeyQueryRule'] = cache_key_rule + else: + config['cacheKeyQueryRule'] = cache['cacheKeyQueryRule'] + + if performance_configuration: + config['performanceConfiguration'] = performance_configuration + + return self.cdn_configuration.updateDomainMapping(config) + + def get_cdn_instance_by_hostname(self, hostname): + """Get the cdn object detail. + + :param string hostname: The CDN identifier. + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + result = None + cdn_list = self.cdn_configuration.listDomainMappings() + for cdn in cdn_list: + if cdn.get('domain') == hostname: + result = cdn + break + + return result + + @staticmethod + def get_cache_key_query_rule(cache_type, cache): + """Get the cdn object detail. + + :param string cache_type: Cache type. + :param cache: Cache description. + + :return: string value. + """ + if 'description' not in cache: + raise SoftLayer.SoftLayerError('Please add a description to be able to update the' + ' cache.') + cache_result = '%s: %s' % (cache_type, cache['description']) + + return cache_result diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst index e334cd6f3..3b749995c 100644 --- a/docs/cli/cdn.rst +++ b/docs/cli/cdn.rst @@ -27,3 +27,7 @@ Interacting with CDN .. click:: SoftLayer.CLI.cdn.purge:cli :prog: cdn purge :show-nested: + +.. click:: SoftLayer.CLI.cdn.edit:cli + :prog: cdn edit + :show-nested: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index cb3c59e43..2a1cf627a 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -95,3 +95,31 @@ def test_remove_origin(self): self.assert_no_fail(result) self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") + + def test_edit_header(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--header=www.test.com']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('www.test.com', header_result['Header']) + + def test_edit_http_port(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--http-port=83']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(83, header_result['Http Port']) + + def test_edit_respect_headers(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--respect-headers=1']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(True, header_result['Respect Headers']) + + def test_edit_cache(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['CacheKeyQueryRule']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 32f9ea9e8..d7f76bbd9 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import fixtures from SoftLayer.managers import cdn from SoftLayer import testing @@ -102,3 +103,38 @@ def test_purge_content(self): self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge', 'createPurge', args=args) + + def test_cdn_edit(self): + hostname = 'test.example.com' + header = 'www.test.com' + origin = '1.1.1.1' + result = self.cdn_client.edit(hostname, header=header, origin=origin) + + self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. + updateDomainMapping, result) + + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'updateDomainMapping', + args=({ + 'uniqueId': '9934111111111', + 'originType': 'HOST_SERVER', + 'protocol': 'HTTP', + 'path': '/', + 'vendorName': 'akamai', + 'cname': 'cdnakauuiet7s6u6.cdnedge.bluemix.net', + 'domain': 'test.example.com', + 'httpPort': 80, + 'header': 'www.test.com', + 'origin': '1.1.1.1' + },) + ) + + def test_cdn_instance_by_hostname(self): + hostname = 'test.example.com' + result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings + self.assertEqual(expected_result[0], result) + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'listDomainMappings',) From a48c7c5aed9a33d8663b959456a51d64e025aa4c Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:46:00 -0400 Subject: [PATCH 0149/1050] create a new commands on slcli that create/cancel a VMware licenses simulate to IBMCloud portal --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/cancel.py | 37 ++++++++++++++ SoftLayer/CLI/licenses/create.py | 33 ++++++++++++ SoftLayer/CLI/routes.py | 4 ++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 32 ++++++++++++ .../fixtures/SoftLayer_Product_Package.py | 26 ++++++++++ .../SoftLayer_Software_AccountLicense.py | 51 +++++++++++++++++++ SoftLayer/managers/license.py | 40 +++++++++++++++ docs/cli/licenses.rst | 12 +++++ tests/CLI/modules/licenses_test.py | 34 +++++++++++++ 10 files changed, 269 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/cancel.py create mode 100644 SoftLayer/CLI/licenses/create.py create mode 100644 SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py create mode 100644 SoftLayer/managers/license.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py new file mode 100644 index 000000000..2dc8e9d81 --- /dev/null +++ b/SoftLayer/CLI/licenses/cancel.py @@ -0,0 +1,37 @@ +"""Cancel VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.license import LicensesManager + + +@click.command() +@click.argument('key') +@click.option('--immediate', is_flag=True, help='Immediate cancellation') +@environment.pass_env +def cli(env, key, immediate): + """Cancel VMware license.""" + + if not immediate: + immediate = False + vmware_find = False + license = LicensesManager(env.client) + + vmware_licenses = license.get_all_objects() + + for vmware in vmware_licenses: + if vmware.get('key') == key: + vmware_find = True + license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + immediate, + 'Cancel by cli command', + 'Cancel by cli command') + break + + if not vmware_find: + raise exceptions.CLIAbort( + "The VMware not found, try whit another key") diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py new file mode 100644 index 000000000..7c66cacc8 --- /dev/null +++ b/SoftLayer/CLI/licenses/create.py @@ -0,0 +1,33 @@ +"""Order/create a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@environment.pass_env +def cli(env, key, datacenter): + """Order/create a vlan instance.""" + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + item_package = [key] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..b931bd3a6 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -217,6 +217,10 @@ ('nas:list', 'SoftLayer.CLI.nas.list:cli'), ('nas:credentials', 'SoftLayer.CLI.nas.credentials:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create', 'SoftLayer.CLI.licenses.create:cli'), + ('licenses:cancel', 'SoftLayer.CLI.licenses.cancel:cli'), + ('object-storage', 'SoftLayer.CLI.object_storage'), ('object-storage:accounts', 'SoftLayer.CLI.object_storage.list_accounts:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index be702ccba..2202f2501 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -252,3 +252,35 @@ ] } } + +wmware_placeOrder = { + "orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [ + { + "id": 176535, + "itemId": 8109, + "categories": [ + { + "categoryCode": "software_license", + "id": 438, + "name": "Software License" + } + ], + "item": { + "capacity": "1", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 8109, + "keyName": "VMWARE_VSAN_ADVANCE_TIER_III_64_124_6_X", + "softwareDescription": { + "id": 1795, + }, + "thirdPartyPolicyAssignments": [ + { + "id": 29263, + "policyName": "3rd Party Software Terms VMWare v4" + } + ] + } + } + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..2ec118f3a 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "oneTimeFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py new file mode 100644 index 000000000..b4433d104 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py @@ -0,0 +1,51 @@ +getAllObjects = [{ + "capacity": "4", + "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2018-10-22T11:16:48-06:00", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 123654789, + "lastBillDate": "2021-06-03T23:11:22-06:00", + "modifyDate": "2021-06-03T23:11:22-06:00", + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 385054741, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1529, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "version": "6.0", + "requiredUser": "administrator@vsphere.local" + } + }, + { + "capacity": "1", + "key": "CBERT-4RL92-K8999-031K4-AJF5J", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2021-06-09T14:51:38-06:00", + "cycleStartDate": "2021-06-09T14:51:38-06:00", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 369852174, + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 836502628, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1795, + "longDescription": "VMware Virtual SAN Advanced Tier III 6.2", + "manufacturer": "VMware", + "name": "Virtual SAN Advanced Tier III", + "version": "6.2", + } + }] diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py new file mode 100644 index 000000000..84e5e538f --- /dev/null +++ b/SoftLayer/managers/license.py @@ -0,0 +1,40 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_all_objects(self): + """Show the all VM ware licenses of account. + + """ + _mask = '''softwareDescription,billingItem''' + + return self.client.call('SoftLayer_Software_AccountLicense', + 'getAllObjects', mask=_mask) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + reason_cancel, + customer_note, + id=identifier) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..55ca0fe10 --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,12 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create:cli + :prog: licenses create + :show-nested: + +.. click:: SoftLayer.CLI.licenses.cancel:cli + :prog: licenses cancel + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b769e80bd --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,34 @@ +""" + SoftLayer.tests.CLI.modules.licenses_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package + +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.wmware_placeOrder + result = self.run_command(['licenses', + 'create', + '-k', 'VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2', + '-d dal03']) + self.assert_no_fail(result) + + def test_cancel(self): + result = self.run_command(['licenses', + 'cancel', + 'ABCDE-6CJ8L-J8R9H-000R0-CDR70', + '--immediate']) + self.assert_no_fail(result) From 3e41e5b6ca4e9ac3ed55e7412379ba693b98acc9 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:51:49 -0400 Subject: [PATCH 0150/1050] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 2dc8e9d81..c5b5f4bbd 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,5 +1,5 @@ -"""Cancel VMware licenses.""" -# :license: MIT, see LICENSE for more details. +"""Cancel a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. import click from SoftLayer import utils From 15749114ea1b13dccb7ed8233aa1293fff001e7f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:58:11 -0400 Subject: [PATCH 0151/1050] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 2 +- SoftLayer/CLI/licenses/create.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index c5b5f4bbd..931685bda 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -14,7 +14,7 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware license.""" + """Cancel VMware licenses.""" if not immediate: immediate = False diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index 7c66cacc8..d3d779c96 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -13,7 +13,7 @@ @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a vlan instance.""" + """Order/create a Vm licenses instance.""" complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] From 4a15aad12ca62f7392cd4f95617dbf67e31085e2 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:04:04 -0400 Subject: [PATCH 0152/1050] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 931685bda..03d6bea1b 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -18,20 +18,20 @@ def cli(env, key, immediate): if not immediate: immediate = False - vmware_find = False - license = LicensesManager(env.client) + vm_ware_find = False + licenses = LicensesManager(env.client) - vmware_licenses = license.get_all_objects() + vm_ware_licenses = licenses.get_all_objects() - for vmware in vmware_licenses: - if vmware.get('key') == key: - vmware_find = True - license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), immediate, 'Cancel by cli command', 'Cancel by cli command') break - if not vmware_find: + if not vm_ware_find: raise exceptions.CLIAbort( "The VMware not found, try whit another key") From 1417c26c06ba72ab4d88f86f791171aaa7784a2a Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:06:17 -0400 Subject: [PATCH 0153/1050] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 03d6bea1b..8b7007319 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -27,9 +27,9 @@ def cli(env, key, immediate): if vm_ware.get('key') == key: vm_ware_find = True licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') + immediate, + 'Cancel by cli command', + 'Cancel by cli command') break if not vm_ware_find: From 5fbd5e4a90449a83d1d5fd564eddca1251170131 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Jun 2021 17:59:49 -0400 Subject: [PATCH 0154/1050] new method to fix the table disconfigurate when the text data is very long --- SoftLayer/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 6b18b6570..b65d3634a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -406,3 +406,24 @@ def trim_to(string, length=80, tail="..."): return string[:length] + tail else: return string + + +def format_comment(comment, max_line_length): + """Return a string that is length long, added a next line and keep the table format. + + :param string string: String you want to add next line + :param int length: max length for the string + """ + comment_length = 0 + words = comment.split(" ") + formatted_comment = "" + for word in words: + if comment_length + (len(word) + 1) <= max_line_length: + formatted_comment = formatted_comment + word + " " + + comment_length = comment_length + len(word) + 1 + else: + formatted_comment = formatted_comment + "\n" + word + " " + + comment_length = len(word) + 1 + return formatted_comment From 402308b6d2bc7d78dcadee5943a2a0d0935c8d1d Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:03:33 -0400 Subject: [PATCH 0155/1050] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 22c579ac7..ce46692d4 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '-H test', + '--name','test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From ec5efc11a403ad49459b1edd14b0f604d4d035de Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:09:48 -0400 Subject: [PATCH 0156/1050] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ce46692d4..c1417a018 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '--name','test', + '--name', 'test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From e6feb690fda1a85933e74695c78ea515e9c654f8 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 12:13:21 -0400 Subject: [PATCH 0157/1050] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Account.py | 6 +++--- SoftLayer/managers/account.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index cdf6e177f..ab607f4de 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,4 +1,4 @@ -"""Show the all account licenses.""" +"""Show all licenses.""" # :license: MIT, see LICENSE for more details. import click from SoftLayer import utils @@ -11,7 +11,7 @@ @click.command() @environment.pass_env def cli(env): - """return the control panel and VMWare licenses""" + """Show all licenses.""" manager = AccountManager(env.client) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 11d1b26d0..7ca2d7e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1104,7 +1104,7 @@ getActiveAccountLicenses = [{ "accountId": 123456, "capacity": "4", - "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "key": "Y8GNS-7QRNG-OUIJO-MATEI-5GJRM", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1141,7 +1141,7 @@ { "accountId": 123456, "capacity": "4", - "key": "4122M-ABXC05-K829T-098HP-00QJM", + "key": "TSZES-SJF85-04GLD-AXA64-8O1EO", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1179,7 +1179,7 @@ getActiveVirtualLicenses = [{ "id": 12345, "ipAddress": "192.168.23.78", - "key": "PLSK.06866259.0000", + "key": "TEST.60220734.0000", "billingItem": { "categoryCode": "control_panel", "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 841f2e92d..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -307,8 +307,8 @@ def get_network_message_delivery_accounts(self): def get_active_virtual_licenses(self): """Gets all active virtual licenses account. - :returns: active virtual licenses account - """ + :returns: active virtual licenses account + """ _mask = """billingItem[categoryCode,createDate,description], key,id,ipAddress, @@ -320,8 +320,9 @@ def get_active_virtual_licenses(self): def get_active_account_licenses(self): """Gets all active account licenses. - :returns: Active account Licenses - """ + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) From cba97f7e73ee84039de261c7f9fa1e3d4e4ad53f Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 2 Jun 2021 16:33:44 -0400 Subject: [PATCH 0158/1050] #1480 add gateway/firewall name to vlan detail and list command --- SoftLayer/CLI/vlan/detail.py | 37 +++++++++++++++++++++------------ SoftLayer/CLI/vlan/list.py | 15 ++++++------- SoftLayer/managers/network.py | 14 +++++++++++++ tests/CLI/modules/vlan_tests.py | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index 6acfb50cb..323699139 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils @click.command() @@ -30,26 +31,24 @@ def cli(env, identifier, no_vs, no_hardware): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', vlan['id']]) - table.add_row(['number', vlan['vlanNumber']]) + table.add_row(['id', vlan.get('id')]) + table.add_row(['number', vlan.get('vlanNumber')]) table.add_row(['datacenter', - vlan['primaryRouter']['datacenter']['longName']]) + utils.lookup(vlan, 'primaryRouter', 'datacenter', 'longName')]) table.add_row(['primary_router', - vlan['primaryRouter']['fullyQualifiedDomainName']]) - table.add_row(['firewall', - 'Yes' if vlan['firewallInterfaces'] else 'No']) + utils.lookup(vlan, 'primaryRouter', 'fullyQualifiedDomainName')]) + table.add_row(['Gateway/Firewall', get_gateway_firewall(vlan)]) subnets = [] for subnet in vlan.get('subnets', []): subnet_table = formatting.KeyValueTable(['name', 'value']) subnet_table.align['name'] = 'r' subnet_table.align['value'] = 'l' - subnet_table.add_row(['id', subnet['id']]) - subnet_table.add_row(['identifier', subnet['networkIdentifier']]) - subnet_table.add_row(['netmask', subnet['netmask']]) - subnet_table.add_row(['gateway', subnet.get('gateway', '-')]) - subnet_table.add_row(['type', subnet['subnetType']]) - subnet_table.add_row(['usable ips', - subnet['usableIpAddressCount']]) + subnet_table.add_row(['id', subnet.get('id')]) + subnet_table.add_row(['identifier', subnet.get('networkIdentifier')]) + subnet_table.add_row(['netmask', subnet.get('netmask')]) + subnet_table.add_row(['gateway', subnet.get('gateway', formatting.blank())]) + subnet_table.add_row(['type', subnet.get('subnetType')]) + subnet_table.add_row(['usable ips', subnet.get('usableIpAddressCount')]) subnets.append(subnet_table) table.add_row(['subnets', subnets]) @@ -81,3 +80,15 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['hardware', 'none']) env.fout(table) + + +def get_gateway_firewall(vlan): + """Gets the name of a gateway/firewall from a VLAN. """ + + firewall = utils.lookup(vlan, 'networkVlanFirewall', 'fullyQualifiedDomainName') + if firewall: + return firewall + gateway = utils.lookup(vlan, 'attachedNetworkGateway', 'name') + if gateway: + return gateway + return formatting.blank() diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index 44500532a..acb4460e5 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -6,12 +6,13 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI.vlan.detail import get_gateway_firewall from SoftLayer import utils COLUMNS = ['id', 'number', 'name', - 'firewall', + 'Gateway/Firewall', 'datacenter', 'hardware', 'virtual_servers', @@ -45,14 +46,14 @@ def cli(env, sortby, datacenter, number, name, limit): limit=limit) for vlan in vlans: table.add_row([ - vlan['id'], - vlan['vlanNumber'], + vlan.get('id'), + vlan.get('vlanNumber'), vlan.get('name') or formatting.blank(), - 'Yes' if vlan['firewallInterfaces'] else 'No', + get_gateway_firewall(vlan), utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name'), - vlan['hardwareCount'], - vlan['virtualGuestCount'], - vlan['totalPrimaryIpAddressCount'], + vlan.get('hardwareCount'), + vlan.get('virtualGuestCount'), + vlan.get('totalPrimaryIpAddressCount'), ]) env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..840d4f3b9 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -43,6 +43,8 @@ 'totalPrimaryIpAddressCount', 'virtualGuestCount', 'networkSpace', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) DEFAULT_GET_VLAN_MASK = ','.join([ 'firewallInterfaces', @@ -53,6 +55,8 @@ 'hardware', 'subnets', 'virtualGuests', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) @@ -433,6 +437,16 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def get_network_gateway_firewall(self, vlan_id): + """Returns information about a single VLAN. + + :param int id: The unique identifier for the VLAN + :returns: A dictionary containing a large amount of information about + the specified VLAN. + + """ + return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index af2b22290..0ffb80165 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -95,6 +95,42 @@ def test_vlan_edit_failure(self, click): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + def test_vlan_detail_firewall(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'networkVlanFirewall': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + + def test_vlan_detail_gateway(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'attachedNetworkGateway': { + 'id': 54321, + "name": 'support' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) From 6372aa2b3dd4792c4f201610cbca70c7c8ae947d Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 17 Jun 2021 20:22:51 -0400 Subject: [PATCH 0159/1050] #1480 remove duplicated method added on network manager --- SoftLayer/managers/network.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 840d4f3b9..21a4516ac 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -437,16 +437,6 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def get_network_gateway_firewall(self, vlan_id): - """Returns information about a single VLAN. - - :param int id: The unique identifier for the VLAN - :returns: A dictionary containing a large amount of information about - the specified VLAN. - - """ - return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. From 13ae244d527187527f34a5c2df81eef1de71520d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:14:51 -0400 Subject: [PATCH 0160/1050] new feature licenses create-options --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/create_options.py | 28 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 ++ .../fixtures/SoftLayer_Product_Package.py | 26 +++++++++++++++++ SoftLayer/managers/licenses.py | 25 +++++++++++++++++ docs/cli/licenses.rst | 8 ++++++ tests/CLI/modules/licenses_test.py | 13 +++++++++ 7 files changed, 103 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/create_options.py create mode 100644 SoftLayer/managers/licenses.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py new file mode 100644 index 000000000..d4f027f3d --- /dev/null +++ b/SoftLayer/CLI/licenses/create_options.py @@ -0,0 +1,28 @@ +"""Licenses order options for a given VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import licenses +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Server order options for a given chassis.""" + + licenses_manager = licenses.LicensesManager(env.client) + + options = licenses_manager.get_create_options() + + table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + for item in options: + table.add_row([item.get('id'), + utils.trim_to(item.get('description'), 40), + item.get('keyName'), + item.get('prices')[0]['recurringFee']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d6266f95b..9f9e7df6f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -124,6 +124,9 @@ ('email:detail', 'SoftLayer.CLI.email.detail:cli'), ('email:edit', 'SoftLayer.CLI.email.edit:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create-options', 'SoftLayer.CLI.licenses.create_options:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..273ec9970 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py new file mode 100644 index 000000000..3d6525382 --- /dev/null +++ b/SoftLayer/managers/licenses.py @@ -0,0 +1,25 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_create_options(self): + """Returns valid options for ordering Licenses. + + :param string datacenter: short name, like dal09 + """ + + return self.client.call('SoftLayer_Product_Package', 'getItems', + id=301) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..8a0326d0e --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,8 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create-options:cli + :prog: licenses create-options + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b50b4edff --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,13 @@ +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + result = self.run_command(['licenses', 'create-options']) + self.assert_no_fail(result) From 9341bac077d365a0d5ff6a4f9fb1f61724fa165d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:28:52 -0400 Subject: [PATCH 0161/1050] fix the tox tool --- docs/cli/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst index 8a0326d0e..b6d6fe66e 100644 --- a/docs/cli/licenses.rst +++ b/docs/cli/licenses.rst @@ -3,6 +3,6 @@ licenses Commands ================= -.. click:: SoftLayer.CLI.licenses.create-options:cli +.. click:: SoftLayer.CLI.licenses.create_options:cli :prog: licenses create-options :show-nested: \ No newline at end of file From d50b30cbbd9226f3b3c4bb093833b6ff961e53b0 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Jun 2021 11:02:44 -0400 Subject: [PATCH 0162/1050] Fix the code review comments --- SoftLayer/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index b65d3634a..5bac80696 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -408,11 +408,11 @@ def trim_to(string, length=80, tail="..."): return string -def format_comment(comment, max_line_length): +def format_comment(comment, max_line_length=60): """Return a string that is length long, added a next line and keep the table format. - :param string string: String you want to add next line - :param int length: max length for the string + :param string comment: String you want to add next line + :param int max_line_length: max length for the string """ comment_length = 0 words = comment.split(" ") From c8033c5ab7fec80bffc2111a6898a7f71d1039d4 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 22 Jun 2021 15:31:31 -0400 Subject: [PATCH 0163/1050] Refactor and fix the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 15 ++++++++++----- SoftLayer/managers/cdn.py | 24 ++++++++++++------------ tests/CLI/modules/cdn_tests.py | 9 ++++++++- tests/managers/cdn_tests.py | 8 ++++---- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index c6cb052cd..677bcd4d7 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -6,10 +6,11 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() -@click.argument('hostname') +@click.argument('identifier') @click.option('--header', '-H', type=click.STRING, help="Host header." @@ -39,10 +40,14 @@ "the Dynamic content acceleration option is not added because this has a special configuration." ) @environment.pass_env -def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): - """Edit a CDN Account.""" +def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account. + + You can use the hostname or uniqueId as IDENTIFIER. + """ manager = SoftLayer.CDNManager(env.client) + cdn_id = helpers.resolve_id(manager.resolve_ids, identifier, 'CDN') cache_result = {} if cache: @@ -52,7 +57,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor else: cache_result['cacheKeyQueryRule'] = cache[0] - cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + cdn_result = manager.edit(cdn_id, header=header, http_port=http_port, origin=origin, respect_headers=respect_headers, cache=cache_result, performance_configuration=performance_configuration) @@ -70,7 +75,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor table.add_row(['Respect Headers', cdn.get('respectHeaders')]) table.add_row(['Unique Id', cdn.get('uniqueId')]) table.add_row(['Vendor Name', cdn.get('vendorName')]) - table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 42acee390..7189cbaa4 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -9,6 +9,9 @@ from SoftLayer import utils +# pylint: disable=no-self-use,too-many-lines,too-many-instance-attributes + + class CDNManager(utils.IdentifierMixin, object): """Manage Content Delivery Networks in the account. @@ -27,6 +30,7 @@ def __init__(self, client): self.cdn_path = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path'] self.cdn_metrics = self.client['Network_CdnMarketplace_Metrics'] self.cdn_purge = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge'] + self.resolvers = [self._get_ids_from_hostname] def list_cdn(self, **kwargs): """Lists Content Delivery Networks for the active user. @@ -171,11 +175,11 @@ def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date - def edit(self, hostname, header=None, http_port=None, origin=None, + def edit(self, identifier, header=None, http_port=None, origin=None, respect_headers=None, cache=None, performance_configuration=None): """Edit the cdn object. - :param string hostname: The CDN hostname. + :param string identifier: The CDN identifier. :param header: The cdn Host header. :param http_port: The cdn HTTP port. :param origin: The cdn Origin server address. @@ -185,14 +189,10 @@ def edit(self, hostname, header=None, http_port=None, origin=None, :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) - if cdn_instance_detail is None: - raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) - - unique_id = cdn_instance_detail.get('uniqueId') + cdn_instance_detail = self.get_cdn(str(identifier)) config = { - 'uniqueId': unique_id, + 'uniqueId': cdn_instance_detail.get('uniqueId'), 'originType': cdn_instance_detail.get('originType'), 'protocol': cdn_instance_detail.get('protocol'), 'path': cdn_instance_detail.get('path'), @@ -229,17 +229,17 @@ def edit(self, hostname, header=None, http_port=None, origin=None, return self.cdn_configuration.updateDomainMapping(config) - def get_cdn_instance_by_hostname(self, hostname): + def _get_ids_from_hostname(self, hostname): """Get the cdn object detail. :param string hostname: The CDN identifier. :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - result = None + result = [] cdn_list = self.cdn_configuration.listDomainMappings() for cdn in cdn_list: - if cdn.get('domain') == hostname: - result = cdn + if cdn.get('domain', '').lower() == hostname.lower(): + result.append(cdn.get('uniqueId')) break return result diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 2a1cf627a..5bff7e647 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -122,4 +122,11 @@ def test_edit_cache(self): '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) - self.assertEqual('include: test', header_result['CacheKeyQueryRule']) + self.assertEqual('include: test', header_result['Cache key optimization']) + + def test_edit_cache_by_uniqueId(self): + result = self.run_command(['cdn', 'edit', '9934111111111', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index d7f76bbd9..a57fe0b68 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -105,10 +105,10 @@ def test_purge_content(self): args=args) def test_cdn_edit(self): - hostname = 'test.example.com' + identifier = '9934111111111' header = 'www.test.com' origin = '1.1.1.1' - result = self.cdn_client.edit(hostname, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header, origin=origin) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) @@ -132,9 +132,9 @@ def test_cdn_edit(self): def test_cdn_instance_by_hostname(self): hostname = 'test.example.com' - result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + result = self.cdn_client._get_ids_from_hostname(hostname) expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings - self.assertEqual(expected_result[0], result) + self.assertEqual(expected_result[0]['uniqueId'], result[0]) self.assert_called_with( 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', 'listDomainMappings',) From 58736a0eb74d40ea06e768d3c4fdd747f97e3282 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 10:11:49 -0400 Subject: [PATCH 0164/1050] add the pod option --- SoftLayer/CLI/vlan/create.py | 9 +++++++-- tests/CLI/modules/vlan_tests.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1af33aec5..ad45735e7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -10,17 +10,22 @@ @click.command() @click.option('--name', required=False, prompt=True, help="Vlan name") -@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--datacenter', '-d', required=False, help="Datacenter shortname") +@click.option('--pod', '-p', required=False, help="Pod name. E.g dal05.pod01") @click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), help='Network vlan type') @click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), help="Billing rate") @environment.pass_env -def cli(env, name, datacenter, network, billing): +def cli(env, name, datacenter, pod, network, billing): """Order/create a VLAN instance.""" item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + + if pod and not datacenter: + datacenter = pod.split('.')[0] + if not network: item_package = ['PRIVATE_NETWORK_VLAN'] diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index c1417a018..86884dff6 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -121,6 +121,24 @@ def test_create_vlan(self): self.assertEqual(json.loads(result.output), {'id': 123456, 'created': '2021-06-02 15:23:47'}) + def test_create_vlan_pod(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + _mock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '--name', 'test', + '-p TEST00.pod2', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True From 14f19da1be6fcdb8a3b1624833e8f29f7e1136e1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 15:15:33 -0400 Subject: [PATCH 0165/1050] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index ab607f4de..42ddd9a8d 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,11 +1,12 @@ """Show all licenses.""" # :license: MIT, see LICENSE for more details. import click + from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager +from SoftLayer.managers import account @click.command() @@ -13,17 +14,17 @@ def cli(env): """Show all licenses.""" - manager = AccountManager(env.client) + manager = account.AccountManager(env.client) - panel_control = manager.get_active_virtual_licenses() + control_panel = manager.get_active_virtual_licenses() vmwares = manager.get_active_account_licenses() table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', - 'key', 'subnet', 'subnet notes']) + 'key', 'subnet', 'subnet notes'], title="Control Panel Licenses") table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', - 'manufacturer', 'requiredUser']) - for panel in panel_control: + 'manufacturer', 'requiredUser'], title="VMware Licenses") + for panel in control_panel: table_panel.add_row([panel.get('id'), panel.get('ipAddress'), utils.lookup(panel, 'softwareDescription', 'manufacturer'), utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), From bcd52a696c4a040bf03ace6aefbe03da36b6b920 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 17:44:41 -0400 Subject: [PATCH 0166/1050] fix the team code review comments --- SoftLayer/CLI/licenses/cancel.py | 28 ++++----------------- SoftLayer/CLI/licenses/create.py | 21 ++++++++-------- SoftLayer/managers/__init__.py | 2 ++ SoftLayer/managers/license.py | 43 ++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 8b7007319..783ce2857 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,12 +1,10 @@ -"""Cancel a vwmare licenses.""" +"""Cancel a license.""" # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer import utils +import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.managers.license import LicensesManager @click.command() @@ -14,24 +12,8 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware licenses.""" + """Cancel a license.""" - if not immediate: - immediate = False - vm_ware_find = False - licenses = LicensesManager(env.client) + licenses = SoftLayer.LicensesManager(env.client) - vm_ware_licenses = licenses.get_all_objects() - - for vm_ware in vm_ware_licenses: - if vm_ware.get('key') == key: - vm_ware_find = True - licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') - break - - if not vm_ware_find: - raise exceptions.CLIAbort( - "The VMware not found, try whit another key") + env.fout(licenses.cancel_item(key, immediate)) diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index d3d779c96..83c7c5a0d 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -2,28 +2,29 @@ # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer.managers import ordering + +import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting @click.command() -@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--key', '-k', required=True, prompt=True, + help="The VMware License Key. " + "To get could use the product_package::getItems id=301 with name Software License Package" + "E.g VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2") @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a Vm licenses instance.""" + """Order/Create License.""" - complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] - ordering_manager = ordering.OrderingManager(env.client) - result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', - location=datacenter, - item_keynames=item_package, - complex_type=complex_type, - hourly=False) + licenses = SoftLayer.LicensesManager(env.client) + + result = licenses.create(datacenter, item_package) + table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 8053ec70e..1b2ddf6f9 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -17,6 +17,7 @@ from SoftLayer.managers.hardware import HardwareManager from SoftLayer.managers.image import ImageManager from SoftLayer.managers.ipsec import IPSECManager +from SoftLayer.managers.license import LicensesManager from SoftLayer.managers.load_balancer import LoadBalancerManager from SoftLayer.managers.metadata import MetadataManager from SoftLayer.managers.network import NetworkManager @@ -43,6 +44,7 @@ 'HardwareManager', 'ImageManager', 'IPSECManager', + 'LicensesManager', 'LoadBalancerManager', 'MetadataManager', 'NetworkManager', diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index 84e5e538f..c529e7e67 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -6,18 +6,20 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +from SoftLayer.CLI import exceptions +from SoftLayer.managers import ordering +from SoftLayer import utils class LicensesManager(object): - """Manages account lincese.""" + """Manages account license.""" def __init__(self, client): self.client = client def get_all_objects(self): - """Show the all VM ware licenses of account. + """Show the all VMware licenses of an account. """ _mask = '''softwareDescription,billingItem''' @@ -25,16 +27,35 @@ def get_all_objects(self): return self.client.call('SoftLayer_Software_AccountLicense', 'getAllObjects', mask=_mask) - def cancel_item(self, identifier, cancel_immediately, - reason_cancel, customer_note): + def cancel_item(self, key, cancel_immediately=False): """Cancel a billing item immediately, deleting all its data. :param integer identifier: the instance ID to cancel :param string reason_cancel: reason cancel """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - cancel_immediately, - True, - reason_cancel, - customer_note, - id=identifier) + vm_ware_licenses = self.get_all_objects() + vm_ware_find = False + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + 'Cancel by cli command', + 'Cancel by cli command', + id=utils.lookup(vm_ware, 'billingItem', 'id')) + + if not vm_ware_find: + raise exceptions.CLIAbort( + "Unable to find license key: {}".format(key)) + return vm_ware_find + + def create(self, datacenter, item_package): + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + ordering_manager = ordering.OrderingManager(self.client) + return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) From 22f58ce2bb7271dfe3d21bbecdb2050b08e7de30 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 18:10:05 -0400 Subject: [PATCH 0167/1050] fix the team code review comments --- SoftLayer/managers/license.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index c529e7e67..00b854c16 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -51,7 +51,11 @@ def cancel_item(self, key, cancel_immediately=False): return vm_ware_find def create(self, datacenter, item_package): + """Create a license + :param string datacenter: the datacenter shortname + :param string[] item_package: items array + """ complex_type = 'SoftLayer_Container_Product_Order_Software_License' ordering_manager = ordering.OrderingManager(self.client) return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', From f3d271668ee6288570ac8fb933798af2884e6ae7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 25 Jun 2021 18:16:19 -0400 Subject: [PATCH 0168/1050] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 17 +++++++++++++---- SoftLayer/fixtures/SoftLayer_Network_Pod.py | 10 ++++++++++ SoftLayer/managers/network.py | 7 +++++++ tests/CLI/modules/vlan_tests.py | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index ad45735e7..1b95ae8b7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -1,10 +1,11 @@ """Order/create a VLAN instance.""" # :license: MIT, see LICENSE for more details. - import click +import SoftLayer from SoftLayer.managers import ordering from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -22,10 +23,18 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' - + extras = {'name': name} if pod and not datacenter: datacenter = pod.split('.')[0] - + mgr = SoftLayer.NetworkManager(env.client) + pods = mgr.get_router() + for router in pods: + if router.get('name') == pod: + extras['routerId'] = router.get('frontendRouterId') + break + if not extras.get('routerId'): + raise exceptions.CLIAbort( + "Unable to find pod name: {}".format(pod)) if not network: item_package = ['PRIVATE_NETWORK_VLAN'] @@ -35,7 +44,7 @@ def cli(env, name, datacenter, pod, network, billing): item_keynames=item_package, complex_type=complex_type, hourly=billing, - extras={'name': name}) + extras=extras) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Pod.py b/SoftLayer/fixtures/SoftLayer_Network_Pod.py index 4e6088270..0a96521ba 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Pod.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Pod.py @@ -18,5 +18,15 @@ 'frontendRouterId': 1114993, 'frontendRouterName': 'fcr01a.wdc07', 'name': 'wdc07.pod01' + }, + { + 'backendRouterId': 1234567, + 'backendRouterName': 'bcr01a.wdc07', + 'datacenterId': 2017603, + 'datacenterLongName': 'Washington 7', + 'datacenterName': 'wdc07', + 'frontendRouterId': 258741369, + 'frontendRouterName': 'fcr01a.wdc07', + 'name': 'TEST00.pod2' } ] diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..d57c8050c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -774,3 +774,10 @@ def cancel_item(self, identifier, cancel_immediately, reason_cancel, customer_note, id=identifier) + + def get_router(self): + """return routers account. + + Returns routers. + """ + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 86884dff6..e0efe0271 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -130,7 +130,7 @@ def test_create_vlan_pod(self): result = self.run_command(['vlan', 'create', '--name', 'test', - '-p TEST00.pod2', + '-p', 'TEST00.pod2', '--network', 'public', '--billing', 'hourly' ]) From 835d1ee9c5b225f7b1d0e696ccb1c5b7e7a35c1b Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Jun 2021 12:30:17 -0400 Subject: [PATCH 0169/1050] fix the team code review comments --- SoftLayer/CLI/licenses/create_options.py | 7 ++++--- SoftLayer/managers/licenses.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py index d4f027f3d..80e8268a2 100644 --- a/SoftLayer/CLI/licenses/create_options.py +++ b/SoftLayer/CLI/licenses/create_options.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers import licenses +from SoftLayer.managers.licenses import LicensesManager from SoftLayer import utils @@ -14,15 +14,16 @@ def cli(env): """Server order options for a given chassis.""" - licenses_manager = licenses.LicensesManager(env.client) + licenses_manager = LicensesManager(env.client) options = licenses_manager.get_create_options() - table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + table = formatting.Table(['Id', 'description', 'keyName', 'capacity', 'recurringFee']) for item in options: table.add_row([item.get('id'), utils.trim_to(item.get('description'), 40), item.get('keyName'), + item.get('capacity'), item.get('prices')[0]['recurringFee']]) env.fout(table) diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py index 3d6525382..3f2dbb7c4 100644 --- a/SoftLayer/managers/licenses.py +++ b/SoftLayer/managers/licenses.py @@ -5,9 +5,10 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +LICENSE_PACKAGE_ID = 301 + class LicensesManager(object): """Manages account lincese.""" @@ -18,8 +19,7 @@ def __init__(self, client): def get_create_options(self): """Returns valid options for ordering Licenses. - :param string datacenter: short name, like dal09 """ return self.client.call('SoftLayer_Product_Package', 'getItems', - id=301) + id=LICENSE_PACKAGE_ID) From 366dc47256a88b7cbcade41f04d5f2c3f36332a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Jun 2021 18:29:43 -0400 Subject: [PATCH 0170/1050] Fix the Christopher code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++--- SoftLayer/managers/network.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1b95ae8b7..0927daf99 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -24,10 +24,10 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' extras = {'name': name} - if pod and not datacenter: + if pod: datacenter = pod.split('.')[0] mgr = SoftLayer.NetworkManager(env.client) - pods = mgr.get_router() + pods = mgr.get_pods() for router in pods: if router.get('name') == pod: extras['routerId'] = router.get('frontendRouterId') @@ -35,7 +35,7 @@ def cli(env, name, datacenter, pod, network, billing): if not extras.get('routerId'): raise exceptions.CLIAbort( "Unable to find pod name: {}".format(pod)) - if not network: + if network == 'private': item_package = ['PRIVATE_NETWORK_VLAN'] ordering_manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d57c8050c..638ce4f91 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -775,9 +775,13 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_router(self): - """return routers account. + def get_pods(self, datacenter=None): + """Calls SoftLayer_Network_Pod::getAllObjects() - Returns routers. + returns list of all network pods and their routers. """ - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') + _filter = None + if datacenter: + _filter = {"datacenterName": {"operation": datacenter}} + + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) From 4af46bdde1e0d67752b0d85aaa4beee45aa9707a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Jul 2021 17:06:52 -0400 Subject: [PATCH 0171/1050] Fix analysis issues. --- SoftLayer/CLI/block/count.py | 4 ++-- SoftLayer/CLI/core.py | 6 +++--- SoftLayer/CLI/file/count.py | 4 ++-- SoftLayer/CLI/formatting.py | 8 ++++---- SoftLayer/CLI/loadbal/detail.py | 4 ++-- SoftLayer/CLI/loadbal/pools.py | 6 +++--- SoftLayer/CLI/ssl/add.py | 20 ++++++++++++-------- SoftLayer/CLI/ssl/edit.py | 20 ++++++++++++-------- SoftLayer/CLI/template.py | 10 +++++----- SoftLayer/CLI/virt/bandwidth.py | 8 ++++---- SoftLayer/utils.py | 2 +- 11 files changed, 50 insertions(+), 42 deletions(-) diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index dc4fb89c1..9f5fd35cf 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -37,6 +37,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 7257c59d9..1dcd68acb 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -54,17 +54,17 @@ def list_commands(self, ctx): return sorted(env.list_commands(*self.path)) - def get_command(self, ctx, name): + def get_command(self, ctx, cmd_name): """Get command for click.""" env = ctx.ensure_object(environment.Environment) env.load() # Do alias lookup (only available for root commands) if len(self.path) == 0: - name = env.resolve_alias(name) + cmd_name = env.resolve_alias(cmd_name) new_path = list(self.path) - new_path.append(name) + new_path.append(cmd_name) module = env.get_command(*new_path) if isinstance(module, types.ModuleType): return CommandLoader(*new_path, help=module.__doc__ or '') diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index addb14300..215d5c4d7 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -36,6 +36,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 0462197c0..b28c54fe6 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -248,11 +248,11 @@ def __str__(self): class CLIJSONEncoder(json.JSONEncoder): """A JSON encoder which is able to use a .to_python() method on objects.""" - def default(self, obj): + def default(self, o): """Encode object if it implements to_python().""" - if hasattr(obj, 'to_python'): - return obj.to_python() - return super().default(obj) + if hasattr(o, 'to_python'): + return o.to_python() + return super().default(o) class Table(object): diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index eb832d594..35e10f376 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -83,8 +83,8 @@ def lbaas_table(this_lb): # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] - for uuid in pools: - member_col.append(pools[uuid]) + for uuid in pools.values(): + member_col.append(uuid) member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index bfd1d48f7..bf77a4c03 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -114,9 +114,9 @@ def edit(env, identifier, listener, **args): 'sslcert': 'tlsCertificateId' } - for arg in args: - if args[arg]: - new_listener[arg_to_option[arg]] = args[arg] + for key, value in args.items(): + if value: + new_listener[arg_to_option[key]] = value try: mgr.add_lb_listener(uuid, new_listener) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index c017cb5b3..161b48c5e 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,15 +28,19 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - template['certificate'] = open(crt).read() - template['privateKey'] = open(key).read() - if csr: - body = open(csr).read() - template['certificateSigningRequest'] = body + with open(crt) as file_crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + body = file_csr.read() + template['certificateSigningRequest'] = body - if icc: - body = open(icc).read() - template['intermediateCertificate'] = body + with open(icc) as file_icc: + if icc: + body = file_icc.read() + template['intermediateCertificate'] = body manager = SoftLayer.SSLManager(env.client) manager.add_certificate(template) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index 4893ebf8f..da899f34f 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,14 +24,18 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - if crt: - template['certificate'] = open(crt).read() - if key: - template['privateKey'] = open(key).read() - if csr: - template['certificateSigningRequest'] = open(csr).read() - if icc: - template['intermediateCertificate'] = open(icc).read() + with open(crt) as file_crt: + if crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + if key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + template['certificateSigningRequest'] = file_csr.read() + with open(icc) as file_icc: + if icc: + template['intermediateCertificate'] = file_icc.read() if notes: template['notes'] = notes diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 437978f78..012644aa6 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,11 +24,11 @@ def __call__(self, ctx, param, value): if value is None: return - config = configparser.ConfigParser() - ini_str = '[settings]\n' + open( - os.path.expanduser(value), 'r').read() - ini_fp = io.StringIO(ini_str) - config.read_file(ini_fp) + with open(os.path.expanduser(value), 'r') as file_handle: + config = configparser.ConfigParser() + ini_str = '[settings]\n' + file_handle.read() + ini_fp = io.StringIO(ini_str) + config.read_file(ini_fp) # Merge template options with the options passed in args = {} diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 2f29cc7f8..68d3d986c 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -71,15 +71,15 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): {'keyName': 'privateOut_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pri Out'}, ] - for point in formatted_data: - new_row = [point] + for key, value in formatted_data.items(): + new_row = [key] for bw_type in bw_totals: - counter = formatted_data[point].get(bw_type['keyName'], 0) + counter = value.get(bw_type['keyName'], 0) new_row.append(mb_to_gb(counter)) bw_type['sum'] = bw_type['sum'] + counter if counter > bw_type['max']: bw_type['max'] = counter - bw_type['maxDate'] = point + bw_type['maxDate'] = key table.add_row(new_row) for bw_type in bw_totals: diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5bac80696..e38760861 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 03539d86156be95253a245f189e937d94512e721 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 2 Jul 2021 10:24:48 -0400 Subject: [PATCH 0172/1050] fix the toox tool and update --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index e38760861..0641b019c 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 13fb0879752bfff2f4dedd342684a8aa20b706f0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Jul 2021 16:41:55 -0500 Subject: [PATCH 0173/1050] 5.9.6 change log and updates --- CHANGELOG.md | 23 +++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a79b6d82..7736e005f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log +## [5.9.6] - 2021-07-05 +https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 + +#### Improvements +- Updated snap to core20 and edited README #1494 +- Add a table result for `slcli hw upgrade` output. #1488 +- Remove block/file interval option for replica volume. #1497 +- `slcli vlan cancel` should report if a vlan is automatic. #1495 +- New method to manage how long text is in output tables. #1506 +- Fix Tox-analysis issues. #1510 + +#### New Commands +- add new email feature #1483 + + `slcli email list` + + `slcli email detail` + + `slcli email edit` +- `slcli vlan cancel` +- Add slcli account licenses #1501 + + `slcli account licenses` +- Create a new commands on slcli that create/cancel a VMware licenses #1504 + + `slcli licenses create` + + `slcli licenses cancel` + ## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 26a00c4cd..5eddbd2b4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.5' +VERSION = 'v5.9.6' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c2e70f61a..43bacbb48 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.5', + version='5.9.6', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 3835c9a848c9582cb300fd732117e505c8e5c14b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 11:25:41 -0400 Subject: [PATCH 0174/1050] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++++- tests/managers/network_tests.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 0927daf99..a3b88f85b 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -30,7 +30,10 @@ def cli(env, name, datacenter, pod, network, billing): pods = mgr.get_pods() for router in pods: if router.get('name') == pod: - extras['routerId'] = router.get('frontendRouterId') + if network == 'public': + extras['routerId'] = router.get('frontendRouterId') + elif network == 'private': + extras['routerId'] = router.get('backendRouterId') break if not extras.get('routerId'): raise exceptions.CLIAbort( @@ -50,5 +53,6 @@ def cli(env, name, datacenter, pod, network, billing): table.align['value'] = 'l' table.add_row(['id', result['orderId']]) table.add_row(['created', result['orderDate']]) + table.add_row(['name', result['orderDetails']['orderContainers'][0]['name']]) env.fout(table) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 2463ed999..a578fd604 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -624,3 +624,7 @@ def test_vlan_edit(self): self.network.edit(vlan_id, name, note, tags) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject') + + def test_get_all_pods(self): + self.network.get_pods() + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') From 6112c5419dc81b356115f5c51346cb7f5d33d6d6 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 12:20:35 -0400 Subject: [PATCH 0175/1050] fix the unit test and tox tool --- SoftLayer/fixtures/SoftLayer_Product_Order.py | 3 +++ tests/CLI/modules/vlan_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index e420315e3..b0d67d868 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -287,6 +287,9 @@ vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", "orderId": 123456, + "orderDetails": { + "orderContainers": [{ + "name": "test"}]}, "prices": [{ "id": 2018, "itemId": 1071, diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 1397f08b0..204788d4d 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -155,7 +155,7 @@ def test_create_vlan(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) def test_create_vlan_pod(self): _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') @@ -173,7 +173,7 @@ def test_create_vlan_pod(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): From adee15c8e3e9ad07321bd075bb321947a00d221a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:16:53 -0500 Subject: [PATCH 0176/1050] Fixed some doc block issues when generating HTML --- SoftLayer/API.py | 1 + SoftLayer/managers/hardware.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a32491ba7..21f21ffc6 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -31,6 +31,7 @@ 'BaseClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT', + 'IAMClient', ] VALID_CALL_ARGS = set(( diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d7e65694e..0f410b322 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -901,7 +901,7 @@ def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. - :return int. + :return int: """ result = None begin_date_object = datetime.datetime.now() From 6017a35746ef1286205d2dcd09838b9b342e09e4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:23 -0500 Subject: [PATCH 0177/1050] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f3b39abf9..f33420f68 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ test-pypi ] + branches: [ master ] jobs: build-n-publish: @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@master - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.8 - name: Install pypa/build run: >- python -m From 4d755f08ee22e498b32f4d2bece49e079e5af108 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:38 -0500 Subject: [PATCH 0178/1050] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f33420f68..439ed17cb 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ master, test-pypi ] jobs: build-n-publish: From 198937ae1e0cf7f85a73158d0c35c635fe1e70f5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 16:12:28 -0500 Subject: [PATCH 0179/1050] updated release workflow to publish to test pypi --- .github/workflows/release.yml | 30 ++++++++++++++++++++++++- .github/workflows/test_pypi_release.yml | 6 ++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b244a86b..f4fd12536 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release +name: Release Snapcraft and PyPi (Testing) on: release: @@ -20,4 +20,32 @@ jobs: VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` echo Publishing $VERSION on ${{ matrix.arch }} snapcraft release slcli $VERSION stable + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 439ed17cb..aea906c54 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,16 +4,16 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master, test-pypi ] + branches: [test-pypi ] jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install pypa/build From f6af86dafebc2b8f041248c96a0278f7b24d0ad8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:28:42 -0500 Subject: [PATCH 0180/1050] Adding in CodeQL Analysis --- .github/workflows/codeql-analysis.yml | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..19d4bd814 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '41 6 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 1162f3fe9d07071463443331ee38695dbf50f210 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:46:40 -0500 Subject: [PATCH 0181/1050] Create SECURITY.md --- SECURITY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..290f09332 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +Generally only the latest release will be actively worked on and supported. +Version 5.7.2 is the last version that supports python2.7. + +| Version | Supported | +| ------- | ------------------ | +| 5.9.x | :white_check_mark: | +| 5.7.2 | :white_check_mark: | +| < 5.7.2 | :x: | + +## Reporting a Vulnerability + +Create a new [Bug Report](https://github.com/softlayer/softlayer-python/issues/new?assignees=&labels=Bug&template=bug_report.md&title=) to let us know about any vulnerabilities in the code base. From 72f8eb19c8f2affe3aa3a1a35b29448b197530a2 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 8 Jul 2021 11:14:15 -0400 Subject: [PATCH 0182/1050] Refactor the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 4 ++-- SoftLayer/managers/cdn.py | 3 ++- tests/CLI/modules/cdn_tests.py | 16 ++++++---------- tests/managers/cdn_tests.py | 3 +-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index 677bcd4d7..f39e20a02 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -20,7 +20,6 @@ help="HTTP port." ) @click.option('--origin', '-o', - required=True, type=click.STRING, help="Origin server address." ) @@ -43,7 +42,7 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): """Edit a CDN Account. - You can use the hostname or uniqueId as IDENTIFIER. + Note: You can use the hostname or uniqueId as IDENTIFIER. """ manager = SoftLayer.CDNManager(env.client) @@ -77,5 +76,6 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, perf table.add_row(['Vendor Name', cdn.get('vendorName')]) table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) + table.add_row(['Origin server address', cdn.get('originHost')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7189cbaa4..7edb97628 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -199,7 +199,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'vendorName': cdn_instance_detail.get('vendorName'), 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), - 'httpPort': cdn_instance_detail.get('httpPort') + 'httpPort': cdn_instance_detail.get('httpPort'), + 'origin': cdn_instance_detail.get('originHost') } if header: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 5bff7e647..c0a96fee4 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -97,36 +97,32 @@ def test_remove_origin(self): self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") def test_edit_header(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--header=www.test.com']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--header=www.test.com']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('www.test.com', header_result['Header']) def test_edit_http_port(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--http-port=83']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--http-port=83']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(83, header_result['Http Port']) def test_edit_respect_headers(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--respect-headers=1']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--respect-headers=1']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(True, header_result['Respect Headers']) def test_edit_cache(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--cache', 'include-specified', + '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) def test_edit_cache_by_uniqueId(self): - result = self.run_command(['cdn', 'edit', '9934111111111', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', '9934111111111', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index a57fe0b68..7e56f81ab 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -107,8 +107,7 @@ def test_purge_content(self): def test_cdn_edit(self): identifier = '9934111111111' header = 'www.test.com' - origin = '1.1.1.1' - result = self.cdn_client.edit(identifier, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) From 835c5af88eec9191af5845a6615d3e55e6646ea1 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 12 Jul 2021 19:12:20 -0400 Subject: [PATCH 0183/1050] Refactor the cdn edit option. --- SoftLayer/managers/cdn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7edb97628..daa7ff377 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -200,7 +200,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), 'httpPort': cdn_instance_detail.get('httpPort'), - 'origin': cdn_instance_detail.get('originHost') + 'origin': cdn_instance_detail.get('originHost'), + 'header': cdn_instance_detail.get('header') } if header: From 9a2752b96d6cd707f1cac31d90a6f06782721fe6 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Jul 2021 17:52:33 -0400 Subject: [PATCH 0184/1050] Refactor loadbal order-options --- SoftLayer/CLI/loadbal/order.py | 103 ++++++++++++++++------------- tests/CLI/modules/loadbal_tests.py | 8 +-- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 2293da8db..e36b28bc9 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -89,53 +89,64 @@ def order_options(env, datacenter): net_mgr = SoftLayer.NetworkManager(env.client) package = mgr.lbaas_order_options() - for region in package['regions']: - dc_name = utils.lookup(region, 'location', 'location', 'name') - - # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: - continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) - - l_groups = [] - for group in region['location']['location']['groups']: - l_groups.append(group.get('id')) - - # Price lookups - prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) - for item in package['items']: - i_price = {'keyName': item['keyName']} - for price in item.get('prices', []): - if not price.get('locationGroupId'): - i_price['default_price'] = price.get('hourlyRecurringFee') - elif price.get('locationGroupId') in l_groups: - i_price['region_price'] = price.get('hourlyRecurringFee') - prices.append(i_price) - for price in prices: - if price.get('region_price'): - price_table.add_row([price.get('keyName'), price.get('region_price')]) - else: - price_table.add_row([price.get('keyName'), price.get('default_price')]) - - # Vlan/Subnet Lookups - mask = "mask[networkVlan,podName,addressSpace]" - subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) - - for subnet in subnets: - # Only show these types, easier to filter here than in an API call. - if subnet.get('subnetType') != 'PRIMARY' and subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + if not datacenter: + data_table = formatting.KeyValueTable(['Datacenters', 'City']) + for region in package['regions']: + data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) + # print(region) + env.fout(data_table) + click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + "to find pricing information and private subnets for that specific site.") + + else: + for region in package['regions']: + dc_name = utils.lookup(region, 'location', 'location', 'name') + + # Skip locations if they are not the one requested. + if datacenter and dc_name != datacenter: continue - space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) - vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) - subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - - env.fout(this_table) + this_table = formatting.Table( + ['Prices', 'Private Subnets'], + title="{}: {}".format(region['keyname'], region['description']) + ) + + l_groups = [] + for group in region['location']['location']['groups']: + l_groups.append(group.get('id')) + + # Price lookups + prices = [] + price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + for item in package['items']: + i_price = {'keyName': item['keyName']} + for price in item.get('prices', []): + if not price.get('locationGroupId'): + i_price['default_price'] = price.get('hourlyRecurringFee') + elif price.get('locationGroupId') in l_groups: + i_price['region_price'] = price.get('hourlyRecurringFee') + prices.append(i_price) + for price in prices: + if price.get('region_price'): + price_table.add_row([price.get('keyName'), price.get('region_price')]) + else: + price_table.add_row([price.get('keyName'), price.get('default_price')]) + + # Vlan/Subnet Lookups + mask = "mask[networkVlan,podName,addressSpace]" + subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + + for subnet in subnets: + # Only show these types, easier to filter here than in an API call. + if subnet.get('subnetType') != 'PRIMARY' and \ + subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + continue + space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) + vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) + subnet_table.add_row([subnet.get('id'), space, vlan]) + this_table.add_row([price_table, subnet_table]) + + env.fout(this_table) @click.command() diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8576a8292..5a9a91134 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -258,11 +258,11 @@ def test_verify_order(self): self.assert_no_fail(result) def test_order_options(self): - mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = SoftLayer_Product_Package.getAllObjectsLoadbal + fault_string = 'Use `slcli lb order-options --datacenter `' \ + ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - - self.assert_no_fail(result) + self.assertIn("ERROR: {}".format(fault_string), + result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From 98f4a40541dce485280e8389b7434df50e521009 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 27 Jul 2021 16:33:57 -0400 Subject: [PATCH 0185/1050] fix the network space is empty on subnet detail --- SoftLayer/CLI/subnet/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 7eb934431..e4ddc2f9e 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -26,7 +26,7 @@ def cli(env, identifier, no_vs, no_hardware): subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, name='subnet') - mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware]' + mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware, networkVlan[networkSpace]]' subnet = mgr.get_subnet(subnet_id, mask=mask) From d4c55d2338156bbf929967db28e08cc8068c7e39 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 13:54:05 -0500 Subject: [PATCH 0186/1050] changed name of the snapcraft release build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4fd12536..b009483c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: types: [published] jobs: - release: + snap-release: runs-on: ubuntu-18.04 strategy: matrix: From 19d98a686cc4c8169919ffeb0d7e79dd81b37fd3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 15:55:38 -0400 Subject: [PATCH 0187/1050] slcli server create-options dal13 Error --- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 26b5dadff..5e91c997c 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -161,3 +161,8 @@ def test_get_active_account_licenses(self): def test_get_active_virtual_licenses(self): self.manager.get_active_virtual_licenses() self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") + + def test_get_routers_with_datacenter(self): + self.manager.get_routers('dal13') + object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file From d4591f058470b233c7e29fc4bcfb22198cead406 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 16:02:23 -0400 Subject: [PATCH 0188/1050] fix the tox analysis --- tests/managers/account_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 5e91c997c..24efd603e 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -165,4 +165,4 @@ def test_get_active_virtual_licenses(self): def test_get_routers_with_datacenter(self): self.manager.get_routers('dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} - self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From 8c1677a917832ef98d207623aa7ec90ff3ba4e14 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 16:03:22 -0500 Subject: [PATCH 0189/1050] prevents --version from looking at environment variables --- README.rst | 20 ++++++++++++++++++++ SoftLayer/CLI/core.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fc05a3503..7cf2e65b3 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,26 @@ InsecurePlatformWarning Notice ------------------------------ This library relies on the `requests `_ library to make HTTP requests. On Python versions below Python 2.7.9, requests has started emitting a security warning (InsecurePlatformWarning) due to insecurities with creating SSL connections. To resolve this, upgrade to Python 2.7.9+ or follow the instructions here: http://stackoverflow.com/a/29099439. +Basic Usage +----------- + +- `The Complete Command Directory `_ + +Advanced Usage +-------------- + +You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example + +.. code-block:: bash + $ export SLCLI_VERBOSE=3 + $ export SLCLI_FORMAT=json + $ slcli vs list + +is equivalent to + +.. code-block:: bash + $ slcli -vvv --format=json vs list + Getting Help ------------ Bugs and feature requests about this library should have a `GitHub issue `_ opened about them. diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 1dcd68acb..0f7c12f8c 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -129,7 +129,7 @@ def get_version_message(ctx, param, value): required=False, help="Use demo data instead of actually making API calls") @click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, - help="Show version information.") + help="Show version information.", allow_from_autoenv=False,) @environment.pass_env def cli(env, format='table', From b8e2a4b7b8691d52e912df4cabcae97192a03668 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 17:09:50 -0400 Subject: [PATCH 0190/1050] fix the team code review comments --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 7d104d877..646d37c09 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,7 +21,7 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - routers = account_manager.get_routers(location) + routers = account_manager.get_routers(location=location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, location=None, mask=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 24efd603e..6d515de38 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -163,6 +163,6 @@ def test_get_active_virtual_licenses(self): self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") def test_get_routers_with_datacenter(self): - self.manager.get_routers('dal13') + self.manager.get_routers(location='dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From cbba4eb0f0932aa39e508e2be048f302f5c974b5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:12:42 -0400 Subject: [PATCH 0191/1050] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index e36b28bc9..704fd9704 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -95,7 +95,7 @@ def order_options(env, datacenter): data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) # print(region) env.fout(data_table) - click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + click.secho("Use `slcli lb order-options --datacenter ` " "to find pricing information and private subnets for that specific site.") else: @@ -105,10 +105,6 @@ def order_options(env, datacenter): # Skip locations if they are not the one requested. if datacenter and dc_name != datacenter: continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) l_groups = [] for group in region['location']['location']['groups']: @@ -116,7 +112,7 @@ def order_options(env, datacenter): # Price lookups prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + price_table = formatting.KeyValueTable(['KeyName', 'Cost'], title='Prices') for item in package['items']: i_price = {'keyName': item['keyName']} for price in item.get('prices', []): @@ -134,7 +130,7 @@ def order_options(env, datacenter): # Vlan/Subnet Lookups mask = "mask[networkVlan,podName,addressSpace]" subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan'], title='Private subnet') for subnet in subnets: # Only show these types, easier to filter here than in an API call. @@ -144,9 +140,9 @@ def order_options(env, datacenter): space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - env.fout(this_table) + env.fout(price_table) + env.fout(subnet_table) @click.command() From b43d7342a8d11506a6d2288b106c7fd483c38ee5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:18:30 -0400 Subject: [PATCH 0192/1050] fix the unit test --- tests/CLI/modules/loadbal_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 5a9a91134..0fba53cea 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -261,8 +261,7 @@ def test_order_options(self): fault_string = 'Use `slcli lb order-options --datacenter `' \ ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - self.assertIn("ERROR: {}".format(fault_string), - result.output) + self.assertIn(fault_string, result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From eee1c28877c0cfdc28553aa168c7552aa5e833ec Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 18:43:45 -0400 Subject: [PATCH 0193/1050] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 704fd9704..9df0af1cc 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -103,7 +103,7 @@ def order_options(env, datacenter): dc_name = utils.lookup(region, 'location', 'location', 'name') # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: + if datacenter and dc_name != datacenter.lower(): continue l_groups = [] From 2efe374cc7ea723787adf4d9ce99ff28636ae8f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 4 Aug 2021 17:13:22 -0400 Subject: [PATCH 0194/1050] last fix team code review comments --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask From 05288e9863a9bda034959f0b67ad2803f57daf0b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:26:01 -0500 Subject: [PATCH 0195/1050] v5.9.7 changelog --- CHANGELOG.md | 18 ++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7736e005f..cf0eb2848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## [5.9.7] - 2021-08-04 +https://github.com/softlayer/softlayer-python/compare/v5.9.6...v5.9.7 + +#### Improvements +- Fixed some doc block issues when generating HTML #1513 +- Updates to the Release workflow for publishing to test pypi #1514 + +- Adding in CodeQL Analysis #1517 +- Create SECURITY.md #1518 +- Fix the network space is empty on subnet detail #1523 +- Prevents SLCLI_VERSION environment variable from breaking things #1527 +- Refactor loadbal order-options #1521 +- slcli server create-options dal13 Error #1526 + +#### New Commands +- add new feature on vlan cli #1499 + + `slcli vlan create` + ## [5.9.6] - 2021-07-05 https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 5eddbd2b4..6d55ed2df 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.6' +VERSION = 'v5.9.7' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 43bacbb48..4eec2cad5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.6', + version='5.9.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 7c6fb218811663c401b402abd4a3c62006f1ba74 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:40:29 -0500 Subject: [PATCH 0196/1050] Fixed formatting in README --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 7cf2e65b3..15d5bcca3 100644 --- a/README.rst +++ b/README.rst @@ -87,6 +87,7 @@ Advanced Usage You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example .. code-block:: bash + $ export SLCLI_VERBOSE=3 $ export SLCLI_FORMAT=json $ slcli vs list @@ -94,6 +95,7 @@ You can automatically set some parameters via environment variables with by usin is equivalent to .. code-block:: bash + $ slcli -vvv --format=json vs list Getting Help From 3dcfff51119f6e0358d1738a51c8e5c4b7180625 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 12 Aug 2021 18:27:41 -0400 Subject: [PATCH 0197/1050] #1530 fix code blocks formatting of The Solution section docs --- docs/api/client.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index f1692eb6a..c90aa3d88 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -228,11 +228,13 @@ The Solution Using the python dictionary's `.get() `_ is great for non-nested items. :: + print("{}, {}".format(item.get('id'), item.get('hostName'))) Otherwise, this SDK provides a util function to do something similar. Each additional argument passed into `utils.lookup` will go one level deeper into the nested dictionary to find the item requested, returning `None` if a KeyError shows up. :: + itemId = SoftLayer.utils.lookup(item, 'id') itemHostname = SoftLayer.utils.lookup(item, 'hostName') print("{}, {}".format(itemId, itemHostname)) From 499746eb972e0119f7407b67793327567fe5f92a Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 18 Aug 2021 10:58:41 -0400 Subject: [PATCH 0198/1050] #1531 Add retry decorator to documentation --- docs/api/client.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index c90aa3d88..3f7700cbb 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -245,3 +245,11 @@ API Reference .. automodule:: SoftLayer :members: + :ignore-module-all: + +.. automodule:: SoftLayer.API + :members: + :ignore-module-all: + +.. automodule:: SoftLayer.decoration + :members: From 1d4a285260c9bc3126840e67534a8deec3a3e082 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:30:32 -0400 Subject: [PATCH 0199/1050] #1532 Add Utility reference to Documentation --- docs/api/client.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..563212577 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -253,3 +253,6 @@ API Reference .. automodule:: SoftLayer.decoration :members: + +.. automodule:: SoftLayer.utils + :members: From 8a62480a35284866a5848e31df38b7ee8d41de8b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:33:57 -0400 Subject: [PATCH 0200/1050] #1532 Fix Sphinx WARNING: Inline emphasis start-string without end-string. --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0641b019c..929b6524b 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -20,7 +20,7 @@ def lookup(dic, key, *keys): """A generic dictionary access helper. This helps simplify code that uses heavily nested dictionaries. It will - return None if any of the keys in *keys do not exist. + return None if any of the keys in `*keys` do not exist. :: From 5e594202114977c6715b301b244f149c55290d91 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 19:05:01 -0400 Subject: [PATCH 0201/1050] #1533 Add Exceptions to Documentation --- docs/api/client.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..13b488bee 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -249,7 +249,10 @@ API Reference .. automodule:: SoftLayer.API :members: - :ignore-module-all: + :ignore-module-all: + +.. automodule:: SoftLayer.exceptions + :members: .. automodule:: SoftLayer.decoration :members: From 9ec4f324cfea39b8cc8f94ca9ecf3b79f21b2a10 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 31 Aug 2021 15:48:48 -0500 Subject: [PATCH 0202/1050] enforcing encodings on XMLRPC calls so we can safely use unicode characters --- SoftLayer/transports.py | 8 +++-- tests/transport_tests.py | 77 +++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index bd643b568..e243f16f6 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -205,7 +205,8 @@ def __call__(self, request): request.url = '/'.join([self.endpoint_url, request.service]) request.payload = xmlrpc.client.dumps(tuple(largs), methodname=request.method, - allow_none=True) + allow_none=True, + encoding="iso-8859-1") # Prefer the request setting, if it's not None verify = request.verify @@ -214,7 +215,7 @@ def __call__(self, request): try: resp = self.client.request('POST', request.url, - data=request.payload, + data=request.payload.encode(), auth=auth, headers=request.transport_headers, timeout=self.timeout, @@ -273,7 +274,7 @@ def print_reproduceable(self, request): #auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) auth=None url = '$url' -payload = """$payload""" +payload = $payload transport_headers = $transport_headers timeout = $timeout verify = $verify @@ -287,6 +288,7 @@ def print_reproduceable(self, request): safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) safe_payload = re.sub(r'(\s+)', r' ', safe_payload) + safe_payload = safe_payload.encode() substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, timeout=self.timeout, verify=request.verify, cert=request.cert, proxy=_proxies_dict(self.proxy)) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index c23f1054f..d09a65c51 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -48,7 +48,7 @@ def set_up(self): def test_call(self, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -63,7 +63,7 @@ def test_call(self, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -134,7 +134,7 @@ def test_identifier(self, request): """ id 1234 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_filter(self, request): @@ -152,7 +152,7 @@ def test_filter(self, request): """ operation ^= prefix -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_limit_offset(self, request): @@ -169,10 +169,10 @@ def test_limit_offset(self, request): self.assertIn(""" resultLimit -""", kwargs['data']) +""".encode(), kwargs['data']) self.assertIn("""limit 10 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_old_mask(self, request): @@ -194,7 +194,7 @@ def test_old_mask(self, request): nested -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_mask_call_no_mask_prefix(self, request): @@ -209,7 +209,7 @@ def test_mask_call_no_mask_prefix(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something.nested]", + "mask[something.nested]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -225,7 +225,7 @@ def test_mask_call_v2(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something[nested]]", + "mask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -241,7 +241,7 @@ def test_mask_call_filteredMask(self, request): args, kwargs = request.call_args self.assertIn( - "filteredMask[something[nested]]", + "filteredMask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -256,7 +256,7 @@ def test_mask_call_v2_dot(self, request): self.transport(req) args, kwargs = request.call_args - self.assertIn("mask.something.nested", + self.assertIn("mask.something.nested".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -287,7 +287,7 @@ def test_print_reproduceable(self): def test_ibm_id_call(self, auth, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -302,7 +302,7 @@ def test_ibm_id_call(self, auth, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -360,6 +360,57 @@ def test_call_large_number_response(self, request): resp = self.transport(req) self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_nonascii_characters(self, request): + request.return_value = self.response + hostname = 'testé' + data = ''' + +getObject + + + + +headers + + + + + + + + +hostname +testé + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ({'hostname': hostname},) + req.transport_user = "testUser" + req.transport_password = "testApiKey" + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + @mock.patch('SoftLayer.transports.requests.Session.request') @pytest.mark.parametrize( From dcbc9fe57f95480ff790bcbc8dc9ac37185ca0fc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 1 Sep 2021 13:43:42 -0500 Subject: [PATCH 0203/1050] Fixing a bunch of tox related issues, moslty unspecified-encoding and use-list-literal errors --- SoftLayer/CLI/autoscale/__init__.py | 1 + SoftLayer/CLI/autoscale/edit.py | 2 +- SoftLayer/CLI/block/count.py | 2 +- SoftLayer/CLI/block/replication/partners.py | 22 +++---------------- SoftLayer/CLI/dns/zone_import.py | 2 +- SoftLayer/CLI/file/count.py | 2 +- SoftLayer/CLI/file/replication/partners.py | 24 +++------------------ SoftLayer/CLI/firewall/edit.py | 2 +- SoftLayer/CLI/hardware/edit.py | 2 +- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/CLI/order/quote.py | 2 +- SoftLayer/CLI/sshkey/add.py | 2 +- SoftLayer/CLI/sshkey/print.py | 2 +- SoftLayer/CLI/ssl/add.py | 8 +++---- SoftLayer/CLI/ssl/download.py | 2 +- SoftLayer/CLI/ssl/edit.py | 8 +++---- SoftLayer/CLI/storage_utils.py | 21 ++++++++++++++++++ SoftLayer/CLI/template.py | 4 ++-- SoftLayer/CLI/virt/create.py | 2 +- SoftLayer/CLI/virt/edit.py | 2 +- SoftLayer/CLI/virt/upgrade.py | 2 +- SoftLayer/config.py | 2 +- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/managers/vs_capacity.py | 2 +- 24 files changed, 55 insertions(+), 67 deletions(-) diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py index e69de29bb..80cd82747 100644 --- a/SoftLayer/CLI/autoscale/__init__.py +++ b/SoftLayer/CLI/autoscale/__init__.py @@ -0,0 +1 @@ +"""Autoscale""" diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py index c470aebbf..94e2165af 100644 --- a/SoftLayer/CLI/autoscale/edit.py +++ b/SoftLayer/CLI/autoscale/edit.py @@ -32,7 +32,7 @@ def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory if userdata: virt_template['userData'] = [{"value": userdata}] elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: virt_template['userData'] = [{"value": userfile_obj.read()}] virt_template['startCpus'] = cpu virt_template['maxMemory'] = memory diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index 9f5fd35cf..ecfba0a53 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -25,7 +25,7 @@ def cli(env, sortby, datacenter): mask=mask) # cycle through all block volumes and count datacenter occurences. - datacenters = dict() + datacenters = {} for volume in block_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/block/replication/partners.py b/SoftLayer/CLI/block/replication/partners.py index f19be0af5..ade8f6b0f 100644 --- a/SoftLayer/CLI/block/replication/partners.py +++ b/SoftLayer/CLI/block/replication/partners.py @@ -6,26 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -COLUMNS = [ - column_helper.Column('ID', ('id',)), - column_helper.Column('Username', ('username',), mask="username"), - column_helper.Column('Account ID', ('accountId',), mask="accountId"), - column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), - column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), - column_helper.Column('Guest ID', ('guestId',), mask="guestId"), - column_helper.Column('Host ID', ('hostId',), mask="hostId"), -] - -DEFAULT_COLUMNS = [ - 'ID', - 'Username', - 'Account ID', - 'Capacity (GB)', - 'Hardware ID', - 'Guest ID', - 'Host ID' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py index 7b47cdb12..e0e3d72d4 100644 --- a/SoftLayer/CLI/dns/zone_import.py +++ b/SoftLayer/CLI/dns/zone_import.py @@ -26,7 +26,7 @@ def cli(env, zonefile, dry_run): """Import zone based off a BIND zone file.""" manager = SoftLayer.DNSManager(env.client) - with open(zonefile) as zone_f: + with open(zonefile, encoding="utf-8") as zone_f: zone_contents = zone_f.read() zone, records, bad_lines = parse_zone_details(zone_contents) diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index 215d5c4d7..cb6ed1a0a 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -24,7 +24,7 @@ def cli(env, sortby, datacenter): file_volumes = file_manager.list_file_volumes(datacenter=datacenter, mask=mask) - datacenters = dict() + datacenters = {} for volume in file_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/file/replication/partners.py b/SoftLayer/CLI/file/replication/partners.py index 866248fdf..b48418b1a 100644 --- a/SoftLayer/CLI/file/replication/partners.py +++ b/SoftLayer/CLI/file/replication/partners.py @@ -6,28 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -COLUMNS = [ - column_helper.Column('ID', ('id',)), - column_helper.Column('Username', ('username',), mask="username"), - column_helper.Column('Account ID', ('accountId',), mask="accountId"), - column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), - column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), - column_helper.Column('Guest ID', ('guestId',), mask="guestId"), - column_helper.Column('Host ID', ('hostId',), mask="hostId"), -] - -# In-line comment to avoid similarity flag with block version - -DEFAULT_COLUMNS = [ - 'ID', - 'Username', - 'Account ID', - 'Capacity (GB)', - 'Hardware ID', - 'Guest ID', - 'Host ID' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py index d2bb5bb2a..0e1f38dac 100644 --- a/SoftLayer/CLI/firewall/edit.py +++ b/SoftLayer/CLI/firewall/edit.py @@ -23,7 +23,7 @@ def parse_rules(content=None): :returns: a list of rules """ rules = content.split(DELIMITER) - parsed_rules = list() + parsed_rules = [] order = 1 for rule in rules: if rule.strip() == '': diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index dc1152c6f..32b1ba5d2 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -40,7 +40,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() if tag: diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index a5b432e82..a3f197d24 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -51,7 +51,7 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_list = list() + disk_list = [] if add_disk: for guest_disk in add_disk: disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index 498dbdc56..f129c02ad 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -43,7 +43,7 @@ def _parse_create_args(client, args): if args.get('userdata'): userdata = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: userdata = userfile.read() if userdata: for hardware in data['hardware']: diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index d80330d7c..570cfcea8 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,7 +33,7 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - with open(path.expanduser(in_file), 'rU') as key_file: + with open(path.expanduser(in_file), 'rU', encoding="utf-8") as key_file: key_text = key_file.read().strip() key_file.close() diff --git a/SoftLayer/CLI/sshkey/print.py b/SoftLayer/CLI/sshkey/print.py index 2bcdcd3be..2e1444d64 100644 --- a/SoftLayer/CLI/sshkey/print.py +++ b/SoftLayer/CLI/sshkey/print.py @@ -26,7 +26,7 @@ def cli(env, identifier, out_file): key = mgr.get_key(key_id) if out_file: - with open(path.expanduser(out_file), 'w') as pub_file: + with open(path.expanduser(out_file), 'w', encoding="utf-8") as pub_file: pub_file.write(key['key']) table = formatting.KeyValueTable(['name', 'value']) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index 161b48c5e..9a579d899 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,16 +28,16 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: body = file_csr.read() template['certificateSigningRequest'] = body - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: body = file_icc.read() template['intermediateCertificate'] = body diff --git a/SoftLayer/CLI/ssl/download.py b/SoftLayer/CLI/ssl/download.py index 77b5247b2..3c7c2dfb6 100644 --- a/SoftLayer/CLI/ssl/download.py +++ b/SoftLayer/CLI/ssl/download.py @@ -30,5 +30,5 @@ def cli(env, identifier): def write_cert(filename, content): """Writes certificate body to the given file path.""" - with open(filename, 'w') as cert_file: + with open(filename, 'w', encoding="utf-8") as cert_file: cert_file.write(content) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index da899f34f..b6dc08e7b 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,16 +24,16 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: if crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: if key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: template['certificateSigningRequest'] = file_csr.read() - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: template['intermediateCertificate'] = file_icc.read() if notes: diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index aa24585eb..3d23e0941 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -144,3 +144,24 @@ def _format_name(obj): 'password', 'allowed_host_id', ] + + +REPLICATION_PARTNER_COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +REPLICATION_PARTNER_DEFAULT = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 012644aa6..9ecbd7172 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,7 +24,7 @@ def __call__(self, ctx, param, value): if value is None: return - with open(os.path.expanduser(value), 'r') as file_handle: + with open(os.path.expanduser(value), 'r', encoding="utf-8") as file_handle: config = configparser.ConfigParser() ini_str = '[settings]\n' + file_handle.read() ini_fp = io.StringIO(ini_str) @@ -58,7 +58,7 @@ def export_to_template(filename, args, exclude=None): exclude.append('format') exclude.append('debug') - with open(filename, "w") as template_file: + with open(filename, "w", encoding="utf-8") as template_file: for k, val in args.items(): if val and k not in exclude: if isinstance(val, tuple): diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 9d07f01d2..ad8b3b35b 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -118,7 +118,7 @@ def _parse_create_args(client, args): if args.get('userdata'): data['userdata'] = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: data['userdata'] = userfile.read() # Get the SSH keys diff --git a/SoftLayer/CLI/virt/edit.py b/SoftLayer/CLI/virt/edit.py index 7c7e07db9..a72caa585 100644 --- a/SoftLayer/CLI/virt/edit.py +++ b/SoftLayer/CLI/virt/edit.py @@ -43,7 +43,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() data['hostname'] = hostname diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index fdfa37822..45e60e573 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -42,7 +42,7 @@ def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize if not (env.skip_confirmations or formatting.confirm("This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_json = list() + disk_json = [] if memory: memory = int(memory / 1024) if resize_disk: diff --git a/SoftLayer/config.py b/SoftLayer/config.py index caa8def10..5ae8c7131 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -118,5 +118,5 @@ def write_config(configuration, config_file=None): if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - with open(config_file, 'w') as file: + with open(config_file, 'w', encoding="utf-8") as file: configuration.write(file) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 7ca2d7e67..35216be76 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -14,7 +14,7 @@ 'globalIdentifier': 'F9329795-4220-4B0A-B970-C86B950667FA', 'id': 201, # 'name': 'private_image2', - 'name': u'a¬ሴ€耀', + 'name': 'a¬ሴ€耀', 'parentId': '', 'publicFlag': False, }] diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 727a881b9..433f2b63a 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -89,7 +89,7 @@ def get_available_routers(self, dc=None): for region in regions: routers[region['keyname']] = [] for location in region['locations']: - location['location']['pods'] = list() + location['location']['pods'] = [] for pod in pods: if pod['datacenterName'] == location['location']['name']: location['location']['pods'].append(pod) From 849a1e4e0fead06e1aead249c3ff27d3211c61d0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 17:09:07 -0400 Subject: [PATCH 0204/1050] Add sensor data to hardware --- SoftLayer/CLI/hardware/sensor.py | 69 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Hardware.py | 28 ++++++++++ SoftLayer/managers/hardware.py | 4 ++ docs/cli/hardware.rst | 4 ++ tests/CLI/modules/server_tests.py | 8 +++ tests/managers/hardware_tests.py | 12 +++-- 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 SoftLayer/CLI/hardware/sensor.py diff --git a/SoftLayer/CLI/hardware/sensor.py b/SoftLayer/CLI/hardware/sensor.py new file mode 100644 index 000000000..cba0f097c --- /dev/null +++ b/SoftLayer/CLI/hardware/sensor.py @@ -0,0 +1,69 @@ +"""Hardware internal Sensor .""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@click.option('--discrete', is_flag=True, default=False, help='Show discrete units associated hardware sensor') +@environment.pass_env +def cli(env, identifier, discrete): + """Retrieve a server’s hardware state via its internal sensors.""" + mgr = SoftLayer.HardwareManager(env.client) + sensors = mgr.get_sensors(identifier) + + temperature_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='temperature Degree c') + + volts_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='volts') + + watts_table = formatting.Table(["sensor", "status", "Reading"], + title='Watts') + + rpm_table = formatting.Table(["sensor", "status", "Reading", "min"], + title='RPM') + + discrete_table = formatting.Table(["sensor", "status", "Reading"], + title='discrete') + + for sensor in sensors: + if sensor.get('sensorUnits') == 'degrees C': + temperature_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) + + if sensor.get('sensorUnits') == 'volts': + volts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerNonCritical'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'Watts': + watts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + + if sensor.get('sensorUnits') == 'RPM': + rpm_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'discrete': + discrete_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + env.fout(temperature_table) + env.fout(rpm_table) + env.fout(volts_table) + env.fout(watts_table) + if discrete: + env.fout(discrete_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a6e42539c..fd53cc43c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -272,6 +272,7 @@ ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), + ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index fd6bf9535..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -59,3 +59,31 @@ } allowAccessToNetworkStorageList = True + +getSensorData = [ + { + "sensorId": "Ambient 1 Temperature", + "sensorReading": "25.000", + "sensorUnits": "degrees C", + "status": "ok", + "upperCritical": "43.000", + "upperNonCritical": "41.000", + "upperNonRecoverable": "46.000" + }, + { + "lowerCritical": "3500.000", + "sensorId": "Fan 1 Tach", + "sensorReading": "6580.000", + "sensorUnits": "RPM", + "status": "ok" + }, { + "sensorId": "IPMI Watchdog", + "sensorReading": "0x0", + "sensorUnits": "discrete", + "status": "0x0080" + }, { + "sensorId": "Avg Power", + "sensorReading": "70.000", + "sensorUnits": "Watts", + "status": "ok" + }] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0f410b322..fe494f83d 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1098,6 +1098,10 @@ def get_components(self, hardware_id, mask=None, filter_component=None): return self.client.call('Hardware_Server', 'getComponents', mask=mask, filter=filter_component, id=hardware_id) + def get_sensors(self, hardware_id): + """Returns Hardware sensor data""" + return self.client.call('Hardware', 'getSensorData', id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index aa95d9141..1f8375cfe 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -127,3 +127,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.upgrade:cli :prog: hardware upgrade :show-nested: + +.. click:: SoftLayer.CLI.hardware.sensor:cli + :prog: hardware sensor + :show-nested: diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3b5498d1a..b558811a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -996,3 +996,11 @@ def test_upgrade(self, confirm_mock): def test_components(self): result = self.run_command(['hardware', 'detail', '100', '--components']) self.assert_no_fail(result) + + def test_sensor(self): + result = self.run_command(['hardware', 'sensor', '100']) + self.assert_no_fail(result) + + def test_sensor_discrete(self): + result = self.run_command(['hardware', 'sensor', '100', '--discrete']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 307b38250..51a6e510d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): @@ -948,6 +948,10 @@ def test_get_components(self): self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + def test_sensor(self): + self.hardware.get_sensors(100) + self.assert_called_with('SoftLayer_Hardware', 'getSensorData') + class HardwareHelperTests(testing.TestCase): From 9317fe356b83d1fea1638f6b7db1b1f28dc6f842 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 18:01:26 -0400 Subject: [PATCH 0205/1050] fix the tox tool --- tests/managers/hardware_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 51a6e510d..a9eada76c 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From 6b17d25b03ba231bd4618b2758d49b3d490ca551 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 15:44:34 -0400 Subject: [PATCH 0206/1050] #1545 remove erroneous print statement in unplanned_event_table function --- SoftLayer/CLI/account/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index b5d8960cb..7d4803b42 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -69,7 +69,6 @@ def unplanned_event_table(events): unplanned_table.align['Subject'] = 'l' unplanned_table.align['Impacted Resources'] = 'l' for event in events: - print(event.get('modifyDate')) unplanned_table.add_row([ event.get('id'), event.get('systemTicketId'), From caa096f4d3cac7735808657ed087022797db5d00 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 16:23:27 -0400 Subject: [PATCH 0207/1050] #1545 add test to validate cli account event json output --- tests/CLI/modules/account_tests.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index fe9c11b0b..8428d3306 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -4,6 +4,8 @@ Tests for the user cli command """ +import json + from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing @@ -37,8 +39,20 @@ def test_event_ack_all(self): self.assert_called_with(self.SLNOE, 'getAllObjects') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) - # slcli account invoice-detail + def test_event_jsonraw_output(self): + # https://github.com/softlayer/softlayer-python/issues/1545 + command = '--format jsonraw account events' + command_params = command.split() + result = self.run_command(command_params) + json_text_tables = result.stdout.split('\n') + # removing an extra item due to an additional Newline at the end of the output + json_text_tables.pop() + # each item in the json_text_tables should be a list + for json_text_table in json_text_tables: + json_table = json.loads(json_text_table) + self.assertIsInstance(json_table, list) + # slcli account invoice-detail def test_invoice_detail(self): result = self.run_command(['account', 'invoice-detail', '1234']) self.assert_no_fail(result) From e94ee4d64ec0b939de41392096943d48bf7b2c20 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Sep 2021 14:39:48 -0500 Subject: [PATCH 0208/1050] Ignoring f-string related messages for tox for now --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 73b9a0007..54cfb633d 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,7 @@ commands = -d consider-using-in \ -d consider-using-dict-comprehension \ -d useless-import-alias \ + -d consider-using-f-string \ --max-args=25 \ --max-branches=20 \ --max-statements=65 \ @@ -60,4 +61,4 @@ commands = [testenv:docs] commands = - python ./docCheck.py \ No newline at end of file + python ./docCheck.py From 3090070a28e67b149157302f10337b9e93fb0075 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:33:10 -0400 Subject: [PATCH 0209/1050] #1541 fix to loadbal details duplicate columns error in members table --- SoftLayer/CLI/loadbal/detail.py | 108 +++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 35e10f376..6712c3ce2 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -29,12 +29,33 @@ def lbaas_table(this_lb): table.align['value'] = 'l' table.add_row(['Id', this_lb.get('id')]) table.add_row(['UUI', this_lb.get('uuid')]) + table.add_row(['Name', this_lb.get('name')]) table.add_row(['Address', this_lb.get('address')]) + table.add_row(['Type', SoftLayer.LoadBalancerManager.TYPE.get(this_lb.get('type'))]) table.add_row(['Location', utils.lookup(this_lb, 'datacenter', 'longName')]) table.add_row(['Description', this_lb.get('description')]) - table.add_row(['Name', this_lb.get('name')]) table.add_row(['Status', "{} / {}".format(this_lb.get('provisioningStatus'), this_lb.get('operatingStatus'))]) + listener_table, pools = get_listener_table(this_lb) + table.add_row(['Protocols', listener_table]) + + member_table = get_member_table(this_lb, pools) + table.add_row(['Members', member_table]) + + hp_table = get_hp_table(this_lb) + table.add_row(['Health Checks', hp_table]) + + l7pool_table = get_l7pool_table(this_lb) + table.add_row(['L7 Pools', l7pool_table]) + + ssl_table = get_ssl_table(this_lb) + table.add_row(['Ciphers', ssl_table]) + + return table + + +def get_hp_table(this_lb): + """Generates a table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) for health in this_lb.get('healthMonitors', []): @@ -47,44 +68,17 @@ def lbaas_table(this_lb): utils.clean_time(health.get('modifyDate')), health.get('provisioningStatus') ]) - table.add_row(['Checks', hp_table]) + return hp_table - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ - l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) - for layer7 in this_lb.get('l7Pools', []): - l7_table.add_row([ - layer7.get('id'), - layer7.get('uuid'), - layer7.get('loadBalancingAlgorithm'), - layer7.get('name'), - layer7.get('protocol'), - utils.clean_time(layer7.get('modifyDate')), - layer7.get('provisioningStatus') - ]) - table.add_row(['L7 Pools', l7_table]) - - pools = {} - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ - listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) - for listener in this_lb.get('listeners', []): - pool = listener.get('defaultPool') - priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) - pools[pool['uuid']] = priv_map - mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) - listener_table.add_row([ - listener.get('uuid'), - listener.get('connectionLimit', 'None'), - mapping, - pool.get('loadBalancingAlgorithm', 'None'), - utils.clean_time(listener.get('modifyDate')), - listener.get('provisioningStatus') - ]) - table.add_row(['Pools', listener_table]) +def get_member_table(this_lb, pools): + """Generates a members table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] + counter = 0 for uuid in pools.values(): - member_col.append(uuid) + member_col.append(f'P{counter}-> {uuid}') + counter += 1 member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ @@ -97,14 +91,54 @@ def lbaas_table(this_lb): for uuid in pools: row.append(get_member_hp(this_lb.get('health'), member.get('uuid'), uuid)) member_table.add_row(row) - table.add_row(['Members', member_table]) + return member_table + +def get_ssl_table(this_lb): + """Generates a ssl table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_SSLCipher/ ssl_table = formatting.Table(['Id', 'Name']) for ssl in this_lb.get('sslCiphers', []): ssl_table.add_row([ssl.get('id'), ssl.get('name')]) - table.add_row(['Ciphers', ssl_table]) - return table + return ssl_table + + +def get_listener_table(this_lb): + """Generates a protocols table from a list of LBaaS devices""" + pools = {} + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ + listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) + for listener in this_lb.get('listeners', []): + pool = listener.get('defaultPool') + priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) + pools[pool['uuid']] = priv_map + mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) + listener_table.add_row([ + listener.get('uuid'), + listener.get('connectionLimit', 'None'), + mapping, + pool.get('loadBalancingAlgorithm', 'None'), + utils.clean_time(listener.get('modifyDate')), + listener.get('provisioningStatus') + ]) + return listener_table, pools + + +def get_l7pool_table(this_lb): + """Generates a l7Pools table from a list of LBaaS devices""" + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ + l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) + for layer7 in this_lb.get('l7Pools', []): + l7_table.add_row([ + layer7.get('id'), + layer7.get('uuid'), + layer7.get('loadBalancingAlgorithm'), + layer7.get('name'), + layer7.get('protocol'), + utils.clean_time(layer7.get('modifyDate')), + layer7.get('provisioningStatus') + ]) + return l7_table def get_member_hp(checks, member_uuid, pool_uuid): From a7690bd7d813fe6038c9a9e1c9160af27b99425b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:36:10 -0400 Subject: [PATCH 0210/1050] 1541 add const type of load balancer as UI, and ibmcloud cli shows --- SoftLayer/managers/load_balancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 3ffa09594..051d2a4e7 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -18,6 +18,11 @@ class LoadBalancerManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + TYPE = { + 1: "Public to Private", + 0: "Private to Private", + 2: "Public to Public", + } def __init__(self, client): self.client = client From 474f5fb70bcc711f54b744f286ba575b4288d434 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:38:46 -0400 Subject: [PATCH 0211/1050] 1541 add validation of some fields to loadbal detail test --- .../fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 11 +++++++++++ tests/CLI/modules/loadbal_tests.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 70e8b64cb..d2a919ae1 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -58,6 +58,17 @@ 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8f', } }, + { + 'clientTimeout': 15, + 'defaultPool': { + 'healthMonitor': { + 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c0' + }, + 'protocol': 'HTTP', + 'protocolPort': 256, + 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8x', + } + }, {'connectionLimit': None, 'createDate': '2019-08-21T17:19:25-04:00', 'defaultPool': {'createDate': '2019-08-21T17:19:25-04:00', diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 0fba53cea..8c5cb1147 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -215,6 +215,9 @@ def test_lb_health_update_fails(self, update_lb_health_monitors): def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + self.assertIn('Id', result.output) + self.assertIn('UUI', result.output) + self.assertIn('Address', result.output) def test_lb_detail_by_name(self): name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') From 046d9be7cb44f74ac7cccd331fa8ac86b14385f8 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Wed, 6 Oct 2021 16:52:27 +0200 Subject: [PATCH 0212/1050] fix: initialized accountmanger closes:https://github.com/softlayer/softlayer-python/issues/1551 --- SoftLayer/managers/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 1b2ddf6f9..2a8955408 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.managers.account import AccountManager from SoftLayer.managers.block import BlockStorageManager from SoftLayer.managers.cdn import CDNManager from SoftLayer.managers.dedicated_host import DedicatedHostManager @@ -33,6 +34,7 @@ from SoftLayer.managers.vs_placement import PlacementManager __all__ = [ + 'AccountManager', 'BlockStorageManager', 'CapacityManager', 'CDNManager', From 297e0de57bdf1d21b6652b3da24c5583e774fc13 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Fri, 8 Oct 2021 11:26:51 +0200 Subject: [PATCH 0213/1050] fix: SoftLayerAPIError import on managers/account.py --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..51c9c889c 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -8,7 +8,7 @@ import logging -from SoftLayer import SoftLayerAPIError +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils # Invalid names are ignored due to long method names and short argument names From 42d9d7ae5722848f6321f334b964717a2fcf5eb4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:27 -0400 Subject: [PATCH 0214/1050] #1550 add loadbal l7policies --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 56 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../SoftLayer_Network_LBaaS_Listener.py | 23 ++++++++ SoftLayer/managers/load_balancer.py | 26 +++++++++ tests/CLI/modules/loadbal_tests.py | 10 ++++ tests/managers/loadbal_tests.py | 10 ++++ 6 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/loadbal/layer7_policy_list.py diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py new file mode 100644 index 000000000..80c408ed1 --- /dev/null +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -0,0 +1,56 @@ +"""List Layer7 policies""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--protocol-id', '-p', + required=False, + type=int, + help="Front-end Protocol identifier") +@environment.pass_env +def policies(env, protocol_id): + """List policies of the front-end protocol (listener).""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + if protocol_id: + l7policies = mgr.get_l7policies(protocol_id) + table = generate_l7policies_table(l7policies, protocol_id) + else: + l7policies = mgr.get_all_l7policies() + table = l7policies_table(l7policies) + env.fout(table) + + +def generate_l7policies_table(l7policies, identifier): + """Takes a list of Layer7 policies and makes a table""" + table = formatting.Table([ + 'Id', 'UUID', 'Name', 'Action', 'Redirect', 'Priority', 'Create Date' + ], title=f"Layer7 policies - protocol ID {identifier}") + + table.align['Name'] = 'l' + table.align['Action'] = 'l' + table.align['Redirect'] = 'l' + for l7policy in sorted(l7policies, key=lambda data: data.get('priority')): + table.add_row([ + l7policy.get('id'), + l7policy.get('uuid'), + l7policy.get('name'), + l7policy.get('action'), + l7policy.get('redirectL7PoolId') or l7policy.get('redirectUrl') or formatting.blank(), + l7policy.get('priority'), + l7policy.get('createDate'), + ]) + return table + + +def l7policies_table(listeners): + """Takes a dict of (protocols: policies list) and makes a list of tables""" + tables = [] + for listener_id, list_policy in listeners.items(): + tables.append(generate_l7policies_table(list_policy, listener_id)) + return tables diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fd53cc43c..c051ad3fd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -204,6 +204,7 @@ ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), ('loadbal:member-add', 'SoftLayer.CLI.loadbal.members:add'), ('loadbal:member-del', 'SoftLayer.CLI.loadbal.members:remove'), + ('loadbal:l7policies', 'SoftLayer.CLI.loadbal.layer7_policy_list:policies'), ('loadbal:pool-add', 'SoftLayer.CLI.loadbal.pools:add'), ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index 57a2459e8..ba814c730 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -41,3 +41,26 @@ 'name': 'ams01', 'statusId': 2 }} + +getL7Policies = [ + {'action': 'REJECT', + 'createDate': '2021-09-08T15:08:35-06:00', + 'id': 123456, + 'modifyDate': None, + 'name': 'test-reject', + 'priority': 2, + 'redirectL7PoolId': None, + 'uuid': '123mock-1234-43c9-b659-12345678mock' + }, + {'action': 'REDIRECT_HTTPS', + 'createDate': '2021-09-08T15:03:53-06:00', + 'id': 432922, + 'modifyDate': None, + 'name': 'test-policy-https-1', + 'priority': 0, + 'redirectL7PoolId': None, + 'redirectUrl': 'url-test-uuid-mock-1234565', + 'uuid': 'test-uuid-mock-1234565' + } +] + diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 051d2a4e7..c2c6b20de 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -160,6 +160,32 @@ def add_lb_listener(self, identifier, listener): return self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', identifier, [listener]) + def get_l7policies(self, identifier): + """Gets Layer7 policies from a listener + + :param identifier: id + """ + + return self.client.call('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', id=identifier) + + def get_all_l7policies(self): + """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). + """ + + mask = 'mask[listeners[l7Policies]]' + lbaas = self.get_lbaas(mask=mask) + listeners = [] + for lb in lbaas: + listeners.extend(lb.get('listeners')) + policies = {} + for protocol in listeners: + if protocol.get('l7Policies'): + listener_id = protocol.get('id') + l7policies = protocol.get('l7Policies') + policies[listener_id] = l7policies + return policies + def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8c5cb1147..cbba6266c 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -181,6 +181,16 @@ def test_lb_member_del(self, click): self.assert_no_fail(result) click.secho.assert_called_with(output, fg='green') + def test_lb_l7policies_list(self): + command = 'loadbal l7policies' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + + def test_lb_l7policies_protocol_list(self): + command = 'loadbal l7policies -p 123456' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.loadbal.health.click') def test_lb_health_manage(self, click): lb_id = '1111111' diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index d8058edcc..7f580474a 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -110,6 +110,16 @@ def test_add_lb_listener(self): self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', args=(uuid, [listener])) + def test_get_l7policies(self): + my_id = 1111111 + self.lb_mgr.get_l7policies(my_id) + self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', identifier=my_id) + + def test_get_all_l7policies(self): + policies = self.lb_mgr.get_all_l7policies() + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertIsInstance(policies, dict) + def test_add_lb_l7_pool(self): uuid = 'aa-bb-cc' pool = {'id': 1} From f621b6f47061dfdad9471c68178db824bd02b7fc Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:56 -0400 Subject: [PATCH 0215/1050] #1550 add loadbal l7policies docs --- docs/cli/loadbal.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index a4116b877..e019e2aa0 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -40,6 +40,9 @@ LBaaS Commands .. click:: SoftLayer.CLI.loadbal.pools:l7pool_del :prog: loadbal l7pool-del :show-nested: +.. click:: SoftLayer.CLI.loadbal.layer7_policy_list:policies + :prog: loadbal l7policies + :show-nested: .. click:: SoftLayer.CLI.loadbal.order:order :prog: loadbal order :show-nested: From 26f2052f2054d53a731280838c32505cfebf2623 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 12 Oct 2021 11:14:32 -0400 Subject: [PATCH 0216/1050] #1550 fix tox issues --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 1 - SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py | 1 - SoftLayer/managers/load_balancer.py | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py index 80c408ed1..563d1c35f 100644 --- a/SoftLayer/CLI/loadbal/layer7_policy_list.py +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -4,7 +4,6 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer import utils @click.command() diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index ba814c730..69cee2113 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -63,4 +63,3 @@ 'uuid': 'test-uuid-mock-1234565' } ] - diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index c2c6b20de..f7f4cedd5 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -170,14 +170,15 @@ def get_l7policies(self, identifier): def get_all_l7policies(self): """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). """ mask = 'mask[listeners[l7Policies]]' lbaas = self.get_lbaas(mask=mask) listeners = [] - for lb in lbaas: - listeners.extend(lb.get('listeners')) + for load_bal in lbaas: + listeners.extend(load_bal.get('listeners')) policies = {} for protocol in listeners: if protocol.get('l7Policies'): From 677f4349abf21b3edd4a6bcd23609b6a0bb0c979 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Mon, 18 Oct 2021 09:51:24 +0530 Subject: [PATCH 0217/1050] Changes for snapshot notification enable and disable option from slcli Changes for snapshot notification enable and disable option from slcli --- .../CLI/block/snapshot/get_notify_status.py | 28 +++++++++++++++++++ .../CLI/block/snapshot/set_notify_status.py | 28 +++++++++++++++++++ .../CLI/file/snapshot/get_notify_status.py | 27 ++++++++++++++++++ .../CLI/file/snapshot/set_notify_status.py | 28 +++++++++++++++++++ SoftLayer/CLI/routes.py | 4 +++ SoftLayer/managers/block.py | 19 +++++++++++++ SoftLayer/managers/file.py | 17 +++++++++++ setup.py | 2 +- 8 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/block/snapshot/get_notify_status.py create mode 100644 SoftLayer/CLI/block/snapshot/set_notify_status.py create mode 100644 SoftLayer/CLI/file/snapshot/get_notify_status.py create mode 100644 SoftLayer/CLI/file/snapshot/set_notify_status.py diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py new file mode 100644 index 000000000..854662dfc --- /dev/null +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -0,0 +1,28 @@ +"""Get the snapshots space usage threshold warning flag setting for specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Get snapshots space usage threshold warning flag setting for a given volume""" + + block_manager = SoftLayer.BlockStorageManager(env.client) + enabled = block_manager.get_block_snapshots_notification_status(volume_id) + + + if (enabled == ''): + click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + % (volume_id)) + elif (enabled == 'True'): + click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + % (volume_id)) + else: + click.echo('DISABLED: Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py new file mode 100644 index 000000000..bef38a40a --- /dev/null +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -0,0 +1,28 @@ +"""Disable/Enable snapshots space usage threshold warning for a specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@click.option('--notification_flag', + help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) +@environment.pass_env +def cli(env, volume_id, notification_flag): + """Enables/Disables snapshot space usage threshold warning for a given volume""" + + if (notification_flag not in ['True', 'False']): + raise exceptions.CLIAbort( + '--notification-flag must be True or False') + + block_manager = SoftLayer.BlockStorageManager(env.client) + disabled = block_manager.set_block_volume_snapshot_notification(volume_id, notification_flag) + + if disabled: + click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification-flag, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py new file mode 100644 index 000000000..707365b3a --- /dev/null +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -0,0 +1,27 @@ +"""Get the snapshots space usage threshold warning flag setting for specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Get snapshots space usage threshold warning flag setting for a given volume""" + + file_manager = SoftLayer.FileStorageManager(env.client) + enabled = file_manager.get_file_snapshots_notification_status(volume_id) + + if (enabled == ''): + click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + % (volume_id)) + elif (enabled == 'True'): + click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + % (volume_id)) + else: + click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py new file mode 100644 index 000000000..83155a5ff --- /dev/null +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -0,0 +1,28 @@ +"""Disable/Enable snapshots space usage threshold warning for a specific volume""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume_id') +@click.option('--notification_flag', + help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) +@environment.pass_env +def cli(env, volume_id, notification_flag): + """Enables/Disables snapshot space usage threshold warning for a given volume""" + + if (notification_flag not in ['True', 'False']): + raise exceptions.CLIAbort( + '--notification-flag must be True or False') + + file_manager = SoftLayer.FileStorageManager(env.client) + disabled = file_manager.set_file_volume_snapshot_notification(volume_id, notification_flag) + + if disabled: + click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification-flag, volume_id)) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fd53cc43c..67cbf4cfa 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -103,6 +103,8 @@ ('block:snapshot-create', 'SoftLayer.CLI.block.snapshot.create:cli'), ('block:snapshot-delete', 'SoftLayer.CLI.block.snapshot.delete:cli'), ('block:snapshot-disable', 'SoftLayer.CLI.block.snapshot.disable:cli'), + ('block:snapshot-set-notification', 'SoftLayer.CLI.block.snapshot.set_notify_status:cli'), + ('block:snapshot-get-notification-status', 'SoftLayer.CLI.block.snapshot.get_notify_status:cli'), ('block:snapshot-enable', 'SoftLayer.CLI.block.snapshot.enable:cli'), ('block:snapshot-schedule-list', 'SoftLayer.CLI.block.snapshot.schedule_list:cli'), ('block:snapshot-list', 'SoftLayer.CLI.block.snapshot.list:cli'), @@ -148,6 +150,8 @@ ('file:snapshot-delete', 'SoftLayer.CLI.file.snapshot.delete:cli'), ('file:snapshot-disable', 'SoftLayer.CLI.file.snapshot.disable:cli'), ('file:snapshot-enable', 'SoftLayer.CLI.file.snapshot.enable:cli'), + ('file:snapshot-set-notification', 'SoftLayer.CLI.file.snapshot.set_notify_status:cli'), + ('file:snapshot-get-notification-status', 'SoftLayer.CLI.file.snapshot.get_notify_status:cli'), ('file:snapshot-schedule-list', 'SoftLayer.CLI.file.snapshot.schedule_list:cli'), ('file:snapshot-list', 'SoftLayer.CLI.file.snapshot.list:cli'), ('file:snapshot-order', 'SoftLayer.CLI.file.snapshot.order:cli'), diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 871c9cb27..b38c0c8b7 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -102,6 +102,25 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) + def set_block_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Enables/Disables snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :param notification-flag: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + + def get_block_snapshots_notification_status(self, volume_id, **kwargs): + """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 734e54081..2a2e017ea 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -129,7 +129,24 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :return: Returns a list of snapshots for the specified volume. """ return self.get_volume_snapshot_list(volume_id, **kwargs) + def set_file_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Enables/Disables snapshot space usage threshold warning for a given volume. + :param volume_id: ID of volume. + :param kwargs: + :param notification-flag: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + + def get_file_snapshots_notification_status(self, volume_id, **kwargs): + """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', diff --git a/setup.py b/setup.py index 4eec2cad5..5248bdb15 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7', + version='5.9.7-dev', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 727cc90ad8899d25bcca9e93f3a0a871c53bbda9 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Tue, 19 Oct 2021 13:01:01 +0530 Subject: [PATCH 0218/1050] Minor cosmetic and parameter updates --- SoftLayer/CLI/block/snapshot/get_notify_status.py | 2 +- SoftLayer/CLI/block/snapshot/set_notify_status.py | 1 - SoftLayer/managers/block.py | 4 ++-- SoftLayer/managers/file.py | 4 ++-- SoftLayer/managers/storage.py | 9 +++++++++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 854662dfc..56fae80be 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -24,5 +24,5 @@ def cli(env, volume_id): click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('DISABLED: Snapshots space usage threshold warning flag setting is disabled for volume %s' + click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) \ No newline at end of file diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index bef38a40a..6cd7084dc 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -15,7 +15,6 @@ @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" - if (notification_flag not in ['True', 'False']): raise exceptions.CLIAbort( '--notification-flag must be True or False') diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index b38c0c8b7..9094bfb32 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -110,7 +110,7 @@ def set_block_volume_snapshot_notification(self, volume_id, notification_flag, * :param notification-flag: Enable/Disable flag for snapshot warning notification. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) def get_block_snapshots_notification_status(self, volume_id, **kwargs): """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. @@ -119,7 +119,7 @@ def get_block_snapshots_notification_status(self, volume_id, **kwargs): :param kwargs: :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 2a2e017ea..5b9fb345d 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -137,7 +137,7 @@ def set_file_volume_snapshot_notification(self, volume_id, notification_flag, ** :param notification-flag: Enable/Disable flag for snapshot warning notification. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id,) + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) def get_file_snapshots_notification_status(self, volume_id, **kwargs): """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. @@ -146,7 +146,7 @@ def get_file_snapshots_notification_status(self, volume_id, **kwargs): :param kwargs: :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id,) + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 44e76c138..4ac9a6ff7 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -112,6 +112,15 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) + def set_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): + """Returns a list of snapshots for the specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of snapshots for the specified volume. + """ + + return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id , **kwargs) def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): From f266dccb24ed744b64e3563c90ab5e4f8317c035 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 20 Oct 2021 21:10:24 +0530 Subject: [PATCH 0219/1050] Removing setup tagging --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5248bdb15..4eec2cad5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7-dev', + version='5.9.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From f7eea013e0ea903e572a3970235f51068979ef30 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 21 Oct 2021 21:49:09 +0530 Subject: [PATCH 0220/1050] Formatting errors resolution Formatting errors mentioned in https://github.com/softlayer/softlayer-python/pull/1554 being resolved. --- .../CLI/block/snapshot/get_notify_status.py | 12 +++++++----- .../CLI/block/snapshot/set_notify_status.py | 19 +++++++++++-------- .../CLI/file/snapshot/get_notify_status.py | 11 +++++++---- .../CLI/file/snapshot/set_notify_status.py | 19 +++++++++++-------- docs/cli/block.rst | 9 +++++++++ docs/cli/file.rst | 11 ++++++++++- 6 files changed, 55 insertions(+), 26 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 56fae80be..9ca845637 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -16,13 +16,15 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_block_snapshots_notification_status(volume_id) - if (enabled == ''): - click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): - click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) \ No newline at end of file + click.echo( + 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 6cd7084dc..cdb260c89 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -9,19 +9,22 @@ @click.command() @click.argument('volume_id') -@click.option('--notification_flag', - help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', - required=True) +@click.option( + '--notification_flag', + help= + 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort( - '--notification-flag must be True or False') + raise exceptions.CLIAbort('--notification-flag must be True or False') block_manager = SoftLayer.BlockStorageManager(env.client) - disabled = block_manager.set_block_volume_snapshot_notification(volume_id, notification_flag) + disabled = block_manager.set_block_volume_snapshot_notification( + volume_id, notification_flag) if disabled: - click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification-flag, volume_id)) + click.echo( + 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification - flag, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 707365b3a..d4afe6654 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -17,11 +17,14 @@ def cli(env, volume_id): enabled = file_manager.get_file_snapshots_notification_status(volume_id) if (enabled == ''): - click.echo('Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): - click.echo('Snapshots space usage threshold warning flag setting is enabled for volume %s' + click.echo( + 'Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: - click.echo('Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) \ No newline at end of file + click.echo( + 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 83155a5ff..112939cc3 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -9,20 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option('--notification_flag', - help='Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', - required=True) +@click.option( + '--notification_flag', + help= + 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + required=True) @environment.pass_env def cli(env, volume_id, notification_flag): """Enables/Disables snapshot space usage threshold warning for a given volume""" if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort( - '--notification-flag must be True or False') + raise exceptions.CLIAbort('--notification-flag must be True or False') file_manager = SoftLayer.FileStorageManager(env.client) - disabled = file_manager.set_file_volume_snapshot_notification(volume_id, notification_flag) + disabled = file_manager.set_file_volume_snapshot_notification( + volume_id, notification_flag) if disabled: - click.echo('Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification-flag, volume_id)) + click.echo( + 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' + % (notification - flag, volume_id)) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 18a324397..b5655709f 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -146,3 +146,12 @@ Block Commands .. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli :prog: block disaster-recovery-failover :show-nested: + + +.. click:: SoftLayer.CLI.block.snapshot.set_notify_status:cli + :prog: block snapshot-set-notification + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.get_notify_status:cli + :prog: block snapshot-get-notification + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 6c914b1a4..7ecc0bc75 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -125,4 +125,13 @@ File Commands .. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli :prog: file disaster-recovery-failover - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.set_notify_status:cli + :prog: file snapshot-set-notification + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.get_notify_status:cli + :prog: file snapshot-get-notification + :show-nested: + From 854087ed6ac5244e9ed024387a3dd5a43b24ed4d Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 21 Oct 2021 22:02:14 +0530 Subject: [PATCH 0221/1050] updating get command --- docs/cli/block.rst | 2 +- docs/cli/file.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index b5655709f..2f3cbfcfc 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -153,5 +153,5 @@ Block Commands :show-nested: .. click:: SoftLayer.CLI.block.snapshot.get_notify_status:cli - :prog: block snapshot-get-notification + :prog: block snapshot-get-notification-status :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 7ecc0bc75..93b5c5331 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -132,6 +132,6 @@ File Commands :show-nested: .. click:: SoftLayer.CLI.file.snapshot.get_notify_status:cli - :prog: file snapshot-get-notification + :prog: file snapshot-get-notification-status :show-nested: From 564e2417bb2d01c91594359e0a200000032f5a29 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 14:13:27 -0400 Subject: [PATCH 0222/1050] #1555 Fix hw billing reports 0 items --- SoftLayer/CLI/hardware/billing.py | 2 +- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 55c68c485..6989489bf 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -31,7 +31,7 @@ def cli(env, identifier): table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Item', 'Recurring Price']) - for item in utils.lookup(result, 'billingItem', 'children') or []: + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 770de045c..18fc5a7d5 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'children': [ + 'nextInvoiceChildren': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { From b3d28a262bea0b092a5d71e1168aa9864609c686 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 15:16:40 -0400 Subject: [PATCH 0223/1050] #1555 update hw billing test --- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- tests/CLI/modules/server_tests.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 18fc5a7d5..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'nextInvoiceChildren': [ + 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index b558811a2..d688a6a90 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -868,19 +868,25 @@ def test_hardware_storage(self): def test_billing(self): result = self.run_command(['hw', 'billing', '123456']) - billing_json = { + hardware_server_billing = { 'Billing Item Id': 6327, 'Id': '123456', 'Provision Date': None, 'Recurring Fee': 1.54, 'Total': 16.08, - 'prices': [{ - 'Item': 'test', - 'Recurring Price': 1 - }] + 'prices': [ + { + 'Item': 'test', + 'Recurring Price': 1 + }, + { + 'Item': 'test2', + 'Recurring Price': 2 + }, + ] } self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), billing_json) + self.assertEqual(json.loads(result.output), hardware_server_billing) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_hw_no_confirm(self, confirm_mock): From da4593ec14163a5ec46195b486aa1b78d62e2576 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Sat, 23 Oct 2021 12:33:49 +0530 Subject: [PATCH 0224/1050] Incorporating review comments --- .../CLI/block/snapshot/set_notify_status.py | 23 +++++++++--------- .../CLI/file/snapshot/set_notify_status.py | 24 +++++++++---------- SoftLayer/managers/storage.py | 18 ++++++++++---- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index cdb260c89..9adffec87 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -9,22 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable', required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - block_manager = SoftLayer.BlockStorageManager(env.client) - disabled = block_manager.set_block_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + status = block_manager.set_block_volume_snapshot_notification( + volume_id, enabled) + + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enabled, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 112939cc3..80b8494fa 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -9,23 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable' , required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - file_manager = SoftLayer.FileStorageManager(env.client) - disabled = file_manager.set_file_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + + status = file_manager.set_file_volume_snapshot_notification( + volume_id, enabled) + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enable, volume_id)) \ No newline at end of file diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 4ac9a6ff7..734e8f9e2 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -112,15 +112,23 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) - def set_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Returns a list of snapshots for the specified volume. + def set_volume_snapshot_notification(self, volume_id, enable): + """Enables/Disables snapshot space usage threshold warning for a given volume. :param volume_id: ID of volume. - :param kwargs: - :return: Returns a list of snapshots for the specified volume. + :param enable: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id , **kwargs) + return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) + + def get_volume_snapshot_notification_status(self, volume_id): + """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): From cce0849fb600447774daf7ff44f33e9b21bf7a3b Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 28 Oct 2021 23:07:48 -0500 Subject: [PATCH 0225/1050] Update API docs link and remove travisCI mention Fixes #1538 --- docs/dev/index.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/dev/index.rst b/docs/dev/index.rst index a0abdcc13..9174186cd 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -72,10 +72,7 @@ your code. You can run only the linting checks by using this command: The project's configuration instructs tox to test against many different versions of Python. A tox test will use as many of those as it can find on your -local computer. Rather than installing all those versions, we recommend that -you point the `Travis `_ continuous integration tool at -your GitHub fork. Travis will run the test against the full suite of Python -versions every time you push new code. +local computer. Using tox to run tests in multiple environments can be very time consuming. If you wish to quickly run the tests in your own environment, you @@ -178,7 +175,7 @@ Developer Resources ------------------- .. toctree:: - SoftLayer API Documentation + SoftLayer API Documentation Source on GitHub Issues Pull Requests From 4ccb9b823048d80a3b06124b6a0255f1ae0bf0b0 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 3 Nov 2021 22:00:09 +0530 Subject: [PATCH 0226/1050] Incorporating review comments --- .../CLI/block/snapshot/get_notify_status.py | 8 ++++---- .../CLI/block/snapshot/set_notify_status.py | 8 ++------ .../CLI/file/snapshot/get_notify_status.py | 8 ++++---- .../CLI/file/snapshot/set_notify_status.py | 9 ++------- SoftLayer/managers/block.py | 19 ------------------- SoftLayer/managers/file.py | 17 ----------------- 6 files changed, 12 insertions(+), 57 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 9ca845637..d7f04ff7f 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,17 +14,17 @@ def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - enabled = block_manager.get_block_snapshots_notification_status(volume_id) + enabled = block_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): click.echo( - 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( - 'Snapshots space usage threshold warning flag setting is enabled for volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: click.echo( - 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 9adffec87..82d30056d 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -18,12 +18,8 @@ def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - if enable: - enabled = 'True' - else: - enabled = 'False' - status = block_manager.set_block_volume_snapshot_notification( - volume_id, enabled) + status = block_manager.set_volume_snapshot_notification( + volume_id, enable) if status: click.echo( diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index d4afe6654..f18b41aba 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -14,17 +14,17 @@ def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - enabled = file_manager.get_file_snapshots_notification_status(volume_id) + enabled = file_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): click.echo( - 'Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( - 'Snapshots space usage threshold warning flag setting is enabled for volume %s' + 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) else: click.echo( - 'Snapshots space usage threshold warning flag setting is disabled for volume %s' + 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 80b8494fa..90b3b7d34 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -18,13 +18,8 @@ def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - if enable: - enabled = 'True' - else: - enabled = 'False' - - status = file_manager.set_file_volume_snapshot_notification( - volume_id, enabled) + status = file_manager.set_volume_snapshot_notification( + volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 9094bfb32..871c9cb27 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -102,25 +102,6 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def set_block_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Enables/Disables snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :param notification-flag: Enable/Disable flag for snapshot warning notification. - :return: Enables/Disables snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) - - def get_block_snapshots_notification_status(self, volume_id, **kwargs): - """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) - def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 5b9fb345d..734e54081 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -129,24 +129,7 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :return: Returns a list of snapshots for the specified volume. """ return self.get_volume_snapshot_list(volume_id, **kwargs) - def set_file_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Enables/Disables snapshot space usage threshold warning for a given volume. - :param volume_id: ID of volume. - :param kwargs: - :param notification-flag: Enable/Disable flag for snapshot warning notification. - :return: Enables/Disables snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id, **kwargs) - - def get_file_snapshots_notification_status(self, volume_id, **kwargs): - """returns Enabled/Disabled snapshot space usage threshold warning for a given volume. - - :param volume_id: ID of volume. - :param kwargs: - :return: Enabled/Disabled snapshot space usage threshold warning for a given volume. - """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id, **kwargs) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, service_offering='storage_as_a_service', From 84b88aae2c18ca312c54b9b884938ab643adc732 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Nov 2021 17:26:26 -0400 Subject: [PATCH 0227/1050] return error internal in vs usage feature when sent the valid-type in lowercase --- SoftLayer/CLI/virt/usage.py | 4 ++-- tests/CLI/modules/vs/vs_tests.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py index 9936d9416..c7404fd45 100644 --- a/SoftLayer/CLI/virt/usage.py +++ b/SoftLayer/CLI/virt/usage.py @@ -30,7 +30,7 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') result = vsi.get_summary_data_usage(vs_id, start_date=start_date, end_date=end_date, - valid_type=valid_type, summary_period=summary_period) + valid_type=valid_type.upper(), summary_period=summary_period) if len(result) == 0: raise exceptions.CLIAbort('No metric data for this range of dates provided') @@ -38,7 +38,7 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): count = 0 counter = 0.00 for data in result: - if valid_type == "MEMORY_USAGE": + if valid_type.upper() == "MEMORY_USAGE": usage_counter = data['counter'] / 2 ** 30 else: usage_counter = data['counter'] diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 97dc52ea4..892a823fd 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -736,6 +736,13 @@ def test_usage_vs_cpu(self): self.assert_no_fail(result) + def test_usage_vs_cpu_lower_case(self): + result = self.run_command( + ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=cpu0', + '--summary_period=300']) + + self.assert_no_fail(result) + def test_usage_vs_memory(self): result = self.run_command( ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=MEMORY_USAGE', From d2ac1a62b8bd745334add524aba3a26ca9a4b531 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 4 Nov 2021 17:37:47 +0530 Subject: [PATCH 0228/1050] TOX formatting resolution --- .../CLI/block/snapshot/get_notify_status.py | 8 +- .../CLI/block/snapshot/set_notify_status.py | 15 +- .../CLI/file/snapshot/get_notify_status.py | 3 +- .../CLI/file/snapshot/set_notify_status.py | 13 +- SoftLayer/managers/storage.py | 253 +++++++++++++----- 5 files changed, 197 insertions(+), 95 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index d7f04ff7f..f247b3c6d 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -4,7 +4,6 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @@ -12,14 +11,13 @@ @environment.pass_env def cli(env, volume_id): """Get snapshots space usage threshold warning flag setting for a given volume""" - block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) if (enabled == ''): - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' - % (volume_id)) + click.echo(""" + Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s + """ % (volume_id)) elif (enabled == 'True'): click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index 82d30056d..51733b381 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -4,24 +4,25 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @click.argument('volume_id') -@click.option('--enable/--disable', default=True, - help= - 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable', +@click.option( + '--enable/--disable', + default=True, + help=""" + Enable/Disable snapshot notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable + """, required=True) @environment.pass_env def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" block_manager = SoftLayer.BlockStorageManager(env.client) - status = block_manager.set_volume_snapshot_notification( - volume_id, enable) + status = block_manager.set_volume_snapshot_notification(volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (enabled, volume_id)) + % (enable, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index f18b41aba..32616f6cd 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -4,7 +4,6 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @@ -18,7 +17,7 @@ def cli(env, volume_id): if (enabled == ''): click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is null. Set to default value enable. For volume %s' + 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' % (volume_id)) elif (enabled == 'True'): click.echo( diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 90b3b7d34..7d5b3e5b7 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -4,23 +4,22 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions @click.command() @click.argument('volume_id') -@click.option('--enable/--disable', default=True, - help= - 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable' , +@click.option( + '--enable/--disable', + default=True, + help='Enable/Disable snapshot notification. Use `slcli file snapshot-set-notification volumeId --enable` to enable', required=True) @environment.pass_env def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" file_manager = SoftLayer.FileStorageManager(env.client) - status = file_manager.set_volume_snapshot_notification( - volume_id, enable) + status = file_manager.set_volume_snapshot_notification(volume_id, enable) if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (enable, volume_id)) \ No newline at end of file + % (enable, volume_id)) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 734e8f9e2..a3176583f 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -9,7 +9,6 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils - # pylint: disable=too-many-public-methods @@ -20,7 +19,6 @@ class StorageManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ - def __init__(self, client): self.configuration = {} self.client = client @@ -69,7 +67,10 @@ def get_volume_details(self, volume_id, **kwargs): 'notes', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getObject', + id=volume_id, + **kwargs) def get_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -87,7 +88,10 @@ def get_volume_access_list(self, volume_id, **kwargs): 'allowedIpAddresses[allowedHost[credential]]', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getObject', + id=volume_id, + **kwargs) def get_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -98,20 +102,18 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): """ if 'mask' not in kwargs: items = [ - 'id', - 'notes', - 'snapshotSizeBytes', - 'storageType[keyName]', - 'snapshotCreationTimestamp', - 'intervalSchedule', - 'hourlySchedule', - 'dailySchedule', - 'weeklySchedule' + 'id', 'notes', 'snapshotSizeBytes', 'storageType[keyName]', + 'snapshotCreationTimestamp', 'intervalSchedule', + 'hourlySchedule', 'dailySchedule', 'weeklySchedule' ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'getSnapshots', + id=volume_id, + **kwargs) + def set_volume_snapshot_notification(self, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume. @@ -120,7 +122,10 @@ def set_volume_snapshot_notification(self, volume_id, enable): :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) + return self.client.call('Network_Storage', + 'setSnapshotNotification', + enable, + id=volume_id) def get_volume_snapshot_notification_status(self, volume_id): """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. @@ -128,10 +133,16 @@ def get_volume_snapshot_notification_status(self, volume_id): :param volume_id: ID of volume. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) - - def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, - ip_address_ids=None, subnet_ids=None): + return self.client.call('Network_Storage', + 'getSnapshotNotificationStatus', + id=volume_id) + + def authorize_host_to_volume(self, + volume_id, + hardware_ids=None, + virtual_guest_ids=None, + ip_address_ids=None, + subnet_ids=None): """Authorizes hosts to Storage Volumes :param volume_id: The File volume to authorize hosts to @@ -142,13 +153,20 @@ def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_i :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects which now have access to the given volume """ - host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, - ip_address_ids, subnet_ids) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', host_templates, id=volume_id) - - def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, - ip_address_ids=None, subnet_ids=None): + host_templates = storage_utils.populate_host_templates( + hardware_ids, virtual_guest_ids, ip_address_ids, subnet_ids) + + return self.client.call('Network_Storage', + 'allowAccessFromHostList', + host_templates, + id=volume_id) + + def deauthorize_host_to_volume(self, + volume_id, + hardware_ids=None, + virtual_guest_ids=None, + ip_address_ids=None, + subnet_ids=None): """Revokes authorization of hosts to File Storage Volumes :param volume_id: The File volume to deauthorize hosts to @@ -159,10 +177,13 @@ def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects which have access to the given File volume """ - host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids, - ip_address_ids, subnet_ids) + host_templates = storage_utils.populate_host_templates( + hardware_ids, virtual_guest_ids, ip_address_ids, subnet_ids) - return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id) + return self.client.call('Network_Storage', + 'removeAccessFromHostList', + host_templates, + id=volume_id) def get_replication_partners(self, volume_id): """Acquires list of replicant volumes pertaining to the given volume. @@ -170,7 +191,9 @@ def get_replication_partners(self, volume_id): :param volume_id: The ID of the primary volume to be replicated :return: Returns an array of SoftLayer_Location objects """ - return self.client.call('Network_Storage', 'getReplicationPartners', id=volume_id) + return self.client.call('Network_Storage', + 'getReplicationPartners', + id=volume_id) def get_replication_locations(self, volume_id): """Acquires list of the datacenters to which a volume can be replicated. @@ -178,9 +201,16 @@ def get_replication_locations(self, volume_id): :param volume_id: The ID of the primary volume to be replicated :return: Returns an array of SoftLayer_Network_Storage objects """ - return self.client.call('Network_Storage', 'getValidReplicationTargetDatacenterLocations', id=volume_id) - - def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=None, os_type=None): + return self.client.call('Network_Storage', + 'getValidReplicationTargetDatacenterLocations', + id=volume_id) + + def order_replicant_volume(self, + volume_id, + snapshot_schedule, + location, + tier=None, + os_type=None): """Places an order for a replicant volume. :param volume_id: The ID of the primary volume to be replicated @@ -199,15 +229,17 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No 'weeklySchedule,storageType[keyName],provisionedIops' block_volume = self.get_volume_details(volume_id, mask=block_mask) - storage_class = storage_utils.block_or_file(block_volume['storageType']['keyName']) + storage_class = storage_utils.block_or_file( + block_volume['storageType']['keyName']) order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, block_volume, storage_class - ) + self, snapshot_schedule, location, tier, block_volume, + storage_class) if storage_class == 'block': if os_type is None: - if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), str): + if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), + str): os_type = block_volume['osType']['keyName'] else: raise exceptions.SoftLayerError( @@ -217,9 +249,15 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No return self.client.call('Product_Order', 'placeOrder', order) - def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None, - duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None, - hourly_billing_flag=False, dependent_duplicate=False): + def order_duplicate_volume(self, + origin_volume_id, + origin_snapshot_id=None, + duplicate_size=None, + duplicate_iops=None, + duplicate_tier_level=None, + duplicate_snapshot_size=None, + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -236,19 +274,23 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl 'storageType[keyName],capacityGb,originalVolumeSize,' \ 'provisionedIops,storageTierLevel,osType[keyName],' \ 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask) - storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName']) + origin_volume = self.get_volume_details(origin_volume_id, + mask=block_mask) + storage_class = storage_utils.block_or_file( + origin_volume['storageType']['keyName']) order = storage_utils.prepare_duplicate_order_object( self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, storage_class, hourly_billing_flag - ) + duplicate_size, duplicate_snapshot_size, storage_class, + hourly_billing_flag) if storage_class == 'block': - if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str): + if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), + str): os_type = origin_volume['osType']['keyName'] else: - raise exceptions.SoftLayerError("Cannot find origin volume's os-type") + raise exceptions.SoftLayerError( + "Cannot find origin volume's os-type") order['osFormatType'] = {'keyName': os_type} @@ -260,7 +302,11 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl return self.client.call('Product_Order', 'placeOrder', order) - def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None): + def order_modified_volume(self, + volume_id, + new_size=None, + new_iops=None, + new_tier_level=None): """Places an order for modifying an existing block volume. :param volume_id: The ID of the volume to be modified @@ -284,8 +330,7 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie volume = self.get_volume_details(volume_id, mask=block_mask) order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) + self, volume, new_iops, new_tier_level, new_size) return self.client.call('Product_Order', 'placeOrder', order) @@ -297,14 +342,19 @@ def volume_set_note(self, volume_id, note): :return: Returns true if success """ template = {'notes': note} - return self.client.call('SoftLayer_Network_Storage', 'editObject', template, id=volume_id) + return self.client.call('SoftLayer_Network_Storage', + 'editObject', + template, + id=volume_id) def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. :param snapshot_id: The ID of the snapshot object to delete. """ - return self.client.call('Network_Storage', 'deleteObject', id=snapshot_id) + return self.client.call('Network_Storage', + 'deleteObject', + id=snapshot_id) def create_snapshot(self, volume_id, notes='', **kwargs): """Creates a snapshot on the given block volume. @@ -313,9 +363,14 @@ def create_snapshot(self, volume_id, notes='', **kwargs): :param string notes: The notes or "name" to assign the snapshot :return: Returns the id of the new snapshot """ - return self.client.call('Network_Storage', 'createSnapshot', notes, id=volume_id, **kwargs) - - def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): + return self.client.call('Network_Storage', + 'createSnapshot', + notes, + id=volume_id, + **kwargs) + + def order_snapshot_space(self, volume_id, capacity, tier, upgrade, + **kwargs): """Orders snapshot space for the given block volume. :param integer volume_id: The id of the volume @@ -329,11 +384,15 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): 'staasVersion,hasEncryptionAtRest' volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) - order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) + order = storage_utils.prepare_snapshot_order_object( + self, volume, capacity, tier, upgrade) return self.client.call('Product_Order', 'placeOrder', order) - def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate=False): + def cancel_snapshot_space(self, + volume_id, + reason='No longer needed', + immediate=False): """Cancels snapshot space for a given volume. :param integer volume_id: The volume ID @@ -345,7 +404,8 @@ def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate= volume = self.get_volume_details(volume_id, mask=object_mask) if 'activeChildren' not in volume['billingItem']: - raise exceptions.SoftLayerError('No snapshot space found to cancel') + raise exceptions.SoftLayerError( + 'No snapshot space found to cancel') children_array = volume['billingItem']['activeChildren'] billing_item_id = None @@ -356,14 +416,21 @@ def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate= break if not billing_item_id: - raise exceptions.SoftLayerError('No snapshot space found to cancel') + raise exceptions.SoftLayerError( + 'No snapshot space found to cancel') if utils.lookup(volume, 'billingItem', 'hourlyFlag'): immediate = True - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + return self.client.call('SoftLayer_Billing_Item', + 'cancelItem', + immediate, + True, + reason, + id=billing_item_id) - def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs): + def enable_snapshots(self, volume_id, schedule_type, retention_count, + minute, hour, day_of_week, **kwargs): """Enables snapshots for a specific block volume at a given schedule :param integer volume_id: The id of the volume @@ -374,8 +441,15 @@ def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, ho :param string day_of_week: Day when to take snapshot :return: Returns whether successfully scheduled or not """ - return self.client.call('Network_Storage', 'enableSnapshots', schedule_type, retention_count, - minute, hour, day_of_week, id=volume_id, **kwargs) + return self.client.call('Network_Storage', + 'enableSnapshots', + schedule_type, + retention_count, + minute, + hour, + day_of_week, + id=volume_id, + **kwargs) def disable_snapshots(self, volume_id, schedule_type): """Disables snapshots for a specific block volume at a given schedule @@ -384,7 +458,10 @@ def disable_snapshots(self, volume_id, schedule_type): :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' :return: Returns whether successfully disabled or not """ - return self.client.call('Network_Storage', 'disableSnapshots', schedule_type, id=volume_id) + return self.client.call('Network_Storage', + 'disableSnapshots', + schedule_type, + id=volume_id) def list_volume_schedules(self, volume_id): """Lists schedules for a given volume @@ -393,7 +470,10 @@ def list_volume_schedules(self, volume_id): :return: Returns list of schedules assigned to a given volume """ object_mask = 'schedules[type,properties[type]]' - volume_detail = self.client.call('Network_Storage', 'getObject', id=volume_id, mask=object_mask) + volume_detail = self.client.call('Network_Storage', + 'getObject', + id=volume_id, + mask=object_mask) return utils.lookup(volume_detail, 'schedules') @@ -404,7 +484,10 @@ def restore_from_snapshot(self, volume_id, snapshot_id): :param integer snapshot_id: The id of the restore point :return: Returns whether succesfully restored or not """ - return self.client.call('Network_Storage', 'restoreFromSnapshot', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', + 'restoreFromSnapshot', + snapshot_id, + id=volume_id) def failover_to_replicant(self, volume_id, replicant_id): """Failover to a volume replicant. @@ -413,7 +496,10 @@ def failover_to_replicant(self, volume_id, replicant_id): :param integer replicant_id: ID of replicant to failover to :return: Returns whether failover was successful or not """ - return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', + 'failoverToReplicant', + replicant_id, + id=volume_id) def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): """Disaster Recovery Failover to a volume replicant. @@ -422,7 +508,10 @@ def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): :param integer replicant: ID of replicant to failover to :return: Returns whether failover to successful or not """ - return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', + 'disasterRecoveryFailoverToReplicant', + replicant_id, + id=volume_id) def failback_from_replicant(self, volume_id): """Failback from a volume replicant. @@ -430,9 +519,14 @@ def failback_from_replicant(self, volume_id): :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) - - def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): + return self.client.call('Network_Storage', + 'failbackFromReplicant', + id=volume_id) + + def cancel_volume(self, + volume_id, + reason='No longer needed', + immediate=False): """Cancels the given storage volume. :param integer volume_id: The volume ID @@ -443,14 +537,20 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): volume = self.get_volume_details(volume_id, mask=object_mask) if 'billingItem' not in volume: - raise exceptions.SoftLayerError("Storage Volume was already cancelled") + raise exceptions.SoftLayerError( + "Storage Volume was already cancelled") billing_item_id = volume['billingItem']['id'] if utils.lookup(volume, 'billingItem', 'hourlyFlag'): immediate = True - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + return self.client.call('SoftLayer_Billing_Item', + 'cancelItem', + immediate, + True, + reason, + id=billing_item_id) def refresh_dupe(self, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent. @@ -458,11 +558,16 @@ def refresh_dupe(self, volume_id, snapshot_id): :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', + 'refreshDuplicate', + snapshot_id, + id=volume_id) def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an independent volume. :param integer volume_id: The id of the volume. """ - return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id) + return self.client.call('Network_Storage', + 'convertCloneDependentToIndependent', + id=volume_id) From e23440d06641357a55406cb79e9ca85d4e2b07f2 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Thu, 4 Nov 2021 22:46:11 +0530 Subject: [PATCH 0229/1050] TOX issue resolution --- SoftLayer/CLI/block/snapshot/get_notify_status.py | 4 ++-- SoftLayer/CLI/file/snapshot/get_notify_status.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index f247b3c6d..25344f812 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,11 +14,11 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) - if (enabled == ''): + if enabled == '': click.echo(""" Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s """ % (volume_id)) - elif (enabled == 'True'): + elif enabled == 'True': click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 32616f6cd..9724046ee 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -15,11 +15,11 @@ def cli(env, volume_id): file_manager = SoftLayer.FileStorageManager(env.client) enabled = file_manager.get_volume_snapshot_notification_status(volume_id) - if (enabled == ''): + if enabled == '': click.echo( 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' % (volume_id)) - elif (enabled == 'True'): + elif enabled == 'True': click.echo( 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' % (volume_id)) From 90631f8ff48185674de97b90c61364f737c62c0e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Nov 2021 16:33:21 -0600 Subject: [PATCH 0230/1050] #1561 added a warning about vs bandwidth summary period, and specifically send in None to the API so the command will at least work --- SoftLayer/CLI/virt/bandwidth.py | 9 ++++++++- tests/CLI/modules/vs/vs_tests.py | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 68d3d986c..c91ff6ffc 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -36,7 +36,14 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): """ vsi = SoftLayer.VSManager(env.client) vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - data = vsi.get_bandwidth_data(vsi_id, start_date, end_date, None, summary_period) + + # Summary period is broken for virtual guests, check VIRT-11733 for a resolution. + # For now, we are going to ignore summary_period and set it to the default the API imposes + if summary_period != 300: + click.secho("""The Summary Period option is currently set to the 300s as the backend API will throw an exception +any other value. This should be resolved in the next version of the slcli.""", fg='yellow') + summary_period = 300 + data = vsi.get_bandwidth_data(vsi_id, start_date, end_date, None, None) title = "Bandwidth Report: %s - %s" % (start_date, end_date) table, sum_table = create_bandwidth_table(data, summary_period, title) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 97dc52ea4..f438a1bb1 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -754,6 +754,7 @@ def test_usage_metric_data_empty(self): self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_bandwidth_vs(self): + self.skipTest("Skipping until VIRT-11733 is released") if sys.version_info < (3, 6): self.skipTest("Test requires python 3.6+") @@ -782,6 +783,7 @@ def test_bandwidth_vs(self): self.assertEqual(output_list[0]['Pub In'], 1.3503) def test_bandwidth_vs_quite(self): + self.skipTest("Skipping until VIRT-11733 is released") result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) self.assert_no_fail(result) From a02f4913ddf1a68e395af28c29bf32e558717994 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 11 Nov 2021 15:09:08 -0400 Subject: [PATCH 0231/1050] Add Item names to vs billing report --- SoftLayer/CLI/virt/billing.py | 6 +-- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 35 ++++++++++---- SoftLayer/managers/vs.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 48 +++++++++---------- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 7312de8d8..ddc785361 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -28,11 +28,11 @@ def cli(env, identifier): table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'provisionDate')]) - price_table = formatting.Table(['Recurring Price']) + price_table = formatting.Table(['description', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 5957949ce..4662b68f8 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -8,16 +8,31 @@ 'id': 6327, 'nextInvoiceTotalRecurringAmount': 1.54, 'children': [ - {'categoryCode': 'port_speed', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_core', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'ram', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_core', - 'nextInvoiceTotalRecurringAmount': 1}, - {'categoryCode': 'guest_disk1', - 'nextInvoiceTotalRecurringAmount': 1}, + { + 'categoryCode': 'ram', + 'description': '1 GB', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'remote_management', + 'description': 'Reboot / Remote Console', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'port_speed', + 'description': '1 Gbps Public & Private Network Uplinks', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'public_port', + 'description': '1 Gbps Public Uplink', + 'nextInvoiceTotalRecurringAmount': 1 + }, + { + 'categoryCode': 'service_port', + 'description': '1 Gbps Private Uplink', + 'nextInvoiceTotalRecurringAmount': 1 + } ], 'package': { "id": 835, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 77410ff4a..75c00127a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -245,7 +245,7 @@ def get_instance(self, instance_id, **kwargs): 'userData,' '''billingItem[id,nextInvoiceTotalRecurringAmount, package[id,keyName], - children[categoryCode,nextInvoiceTotalRecurringAmount], + children[description,categoryCode,nextInvoiceTotalRecurringAmount], orderItem[id, order.userRecord[username], preset.keyName]],''' diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 892a823fd..29007f154 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -323,7 +323,7 @@ def test_create_options_prices(self): def test_create_options_prices_location(self): result = self.run_command(['vs', 'create-options', '--prices', 'dal13', - '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -345,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -400,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) @@ -820,11 +820,11 @@ def test_billing(self): 'Recurring Fee': None, 'Total': 1.54, 'prices': [ - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1}, - {'Recurring Price': 1} + {'description': '1 GB', 'Recurring Price': 1}, + {'description': 'Reboot / Remote Console', 'Recurring Price': 1}, + {'description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, + {'description': '1 Gbps Public Uplink', 'Recurring Price': 1}, + {'description': '1 Gbps Private Uplink', 'Recurring Price': 1} ] } self.assert_no_fail(result) From f6677a7fedafc9c14e58eebdd84dbde064103be8 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Nov 2021 14:50:49 -0400 Subject: [PATCH 0232/1050] fix the team code review --- SoftLayer/CLI/virt/billing.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index ddc785361..7ef1e0884 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -30,7 +30,7 @@ def cli(env, identifier): table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['Provision Date', utils.lookup(result, 'provisionDate')]) - price_table = formatting.Table(['description', 'Recurring Price']) + price_table = formatting.Table(['Description', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 29007f154..187445063 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -820,11 +820,11 @@ def test_billing(self): 'Recurring Fee': None, 'Total': 1.54, 'prices': [ - {'description': '1 GB', 'Recurring Price': 1}, - {'description': 'Reboot / Remote Console', 'Recurring Price': 1}, - {'description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, - {'description': '1 Gbps Public Uplink', 'Recurring Price': 1}, - {'description': '1 Gbps Private Uplink', 'Recurring Price': 1} + {'Description': '1 GB', 'Recurring Price': 1}, + {'Description': 'Reboot / Remote Console', 'Recurring Price': 1}, + {'Description': '1 Gbps Public & Private Network Uplinks', 'Recurring Price': 1}, + {'Description': '1 Gbps Public Uplink', 'Recurring Price': 1}, + {'Description': '1 Gbps Private Uplink', 'Recurring Price': 1} ] } self.assert_no_fail(result) From 34cad8a28df006cb9379cfe9784a21fd272588fb Mon Sep 17 00:00:00 2001 From: Sandro Tosi Date: Sun, 21 Nov 2021 01:05:00 -0500 Subject: [PATCH 0233/1050] Mapping is now in collections.abc this fixes an error running tests: ``` __________________________ TestUtils.test_dict_merge ___________________________ self = def test_dict_merge(self): filter1 = {"virtualGuests": {"hostname": {"operation": "etst"}}} filter2 = {"virtualGuests": {"id": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}}} > result = SoftLayer.utils.dict_merge(filter1, filter2) tests/basic_tests.py:85: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ dct1 = {'virtualGuests': {'hostname': {'operation': 'etst'}}} dct2 = {'virtualGuests': {'id': {'operation': 'orderBy', 'options': [{'name': 'sort', 'value': ['DESC']}]}}} def dict_merge(dct1, dct2): """Recursively merges dct2 and dct1, ideal for merging objectFilter together. :param dct1: A dictionary :param dct2: A dictionary :return: dct1 + dct2 """ dct = dct1.copy() for k, _ in dct2.items(): > if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): E AttributeError: module 'collections' has no attribute 'Mapping' SoftLayer/utils.py:71: AttributeError ``` --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 929b6524b..05ef50471 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -68,7 +68,7 @@ def dict_merge(dct1, dct2): dct = dct1.copy() for k, _ in dct2.items(): - if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): + if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.abc.Mapping)): dct[k] = dict_merge(dct1[k], dct2[k]) else: dct[k] = dct2[k] From 91d72205e8a376a721afd6dcc2c83b666025700f Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 25 Nov 2021 17:22:34 -0400 Subject: [PATCH 0234/1050] fix vs placementgroup list --- SoftLayer/CLI/virt/placementgroup/list.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/virt/placementgroup/list.py b/SoftLayer/CLI/virt/placementgroup/list.py index 94f72af1d..3a7098b23 100644 --- a/SoftLayer/CLI/virt/placementgroup/list.py +++ b/SoftLayer/CLI/virt/placementgroup/list.py @@ -5,6 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.vs_placement import PlacementManager as PlacementManager +from SoftLayer import utils @click.command() @@ -19,12 +20,12 @@ def cli(env): ) for group in result: table.add_row([ - group['id'], - group['name'], - group['backendRouter']['hostname'], - group['rule']['name'], - group['guestCount'], - group['createDate'] + utils.lookup(group, 'id'), + utils.lookup(group, 'name'), + utils.lookup(group, 'backendRouter', 'hostname'), + utils.lookup(group, 'rule', 'name'), + utils.lookup(group, 'guestCount'), + utils.lookup(group, 'createDate') ]) env.fout(table) From 52ee904ab320952598fc0a6757100093307f32f1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:25:02 -0600 Subject: [PATCH 0235/1050] #1568 fixed up snapshot-notification cli commands --- .../CLI/block/snapshot/get_notify_status.py | 14 ++------ .../CLI/file/snapshot/get_notify_status.py | 14 ++------ SoftLayer/managers/storage.py | 20 +++++++---- tests/CLI/modules/block_tests.py | 10 ++++++ tests/CLI/modules/file_tests.py | 10 ++++++ tests/managers/storage_generic_tests.py | 35 +++++++++++++++++++ 6 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 tests/managers/storage_generic_tests.py diff --git a/SoftLayer/CLI/block/snapshot/get_notify_status.py b/SoftLayer/CLI/block/snapshot/get_notify_status.py index 25344f812..f16ef9804 100644 --- a/SoftLayer/CLI/block/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/get_notify_status.py @@ -14,15 +14,7 @@ def cli(env, volume_id): block_manager = SoftLayer.BlockStorageManager(env.client) enabled = block_manager.get_volume_snapshot_notification_status(volume_id) - if enabled == '': - click.echo(""" - Enabled:Snapshots space usage warning flag is null. Set to default value enable. For volume %s - """ % (volume_id)) - elif enabled == 'True': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' - % (volume_id)) + if enabled == 0: + click.echo("Disabled: Snapshots space usage threshold is disabled for volume {}".format(volume_id)) else: - click.echo( - 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) + click.echo("Enabled: Snapshots space usage threshold is enabled for volume {}".format(volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/get_notify_status.py b/SoftLayer/CLI/file/snapshot/get_notify_status.py index 9724046ee..1cddb6a28 100644 --- a/SoftLayer/CLI/file/snapshot/get_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/get_notify_status.py @@ -15,15 +15,7 @@ def cli(env, volume_id): file_manager = SoftLayer.FileStorageManager(env.client) enabled = file_manager.get_volume_snapshot_notification_status(volume_id) - if enabled == '': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag is null. Set to default value enable. For volume %s' - % (volume_id)) - elif enabled == 'True': - click.echo( - 'Enabled:Snapshots space usage threshold warning flag setting is enabled for volume %s' - % (volume_id)) + if enabled == 0: + click.echo("Disabled: Snapshots space usage threshold is disabled for volume {}".format(volume_id)) else: - click.echo( - 'Disabled:Snapshots space usage threshold warning flag setting is disabled for volume %s' - % (volume_id)) + click.echo("Enabled: Snapshots space usage threshold is enabled for volume {}".format(volume_id)) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index a3176583f..980736876 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -19,11 +19,16 @@ class StorageManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + def __init__(self, client): self.configuration = {} self.client = client self.resolvers = [self._get_ids_from_username] + def _get_ids_from_username(self, username): + """Should only be actually called from the block/file manager""" + return [] + def get_volume_count_limits(self): """Returns a list of block volume count limit. @@ -122,10 +127,7 @@ def set_volume_snapshot_notification(self, volume_id, enable): :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', - 'setSnapshotNotification', - enable, - id=volume_id) + return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) def get_volume_snapshot_notification_status(self, volume_id): """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. @@ -133,9 +135,13 @@ def get_volume_snapshot_notification_status(self, volume_id): :param volume_id: ID of volume. :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', - 'getSnapshotNotificationStatus', - id=volume_id) + status = self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) + # A None status is enabled as well. + if status is None: + status = 1 + # We need to force int on the return because otherwise the API will return the string '0' + # instead of either a boolean or real int... + return int(status) def authorize_host_to_volume(self, volume_id, diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 1c6b22e14..9f4499782 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -812,3 +812,13 @@ def test_volume_not_set_note(self, set_note): self.assert_no_fail(result) self.assertIn("Note could not be set!", result.output) + + @mock.patch('SoftLayer.BlockStorageManager.get_volume_snapshot_notification_status') + def test_snapshot_get_notification_status(self, status): + status.side_effect = [None, 1, 0] + expected = ['Enabled', 'Enabled', 'Disabled'] + + for expect in expected: + result = self.run_command(['block', 'snapshot-get-notification-status', '999']) + self.assert_no_fail(result) + self.assertIn(expect, result.output) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index cbe73818c..06595dee0 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -791,3 +791,13 @@ def test_volume_not_set_note(self, set_note): self.assert_no_fail(result) self.assertIn("Note could not be set!", result.output) + + @mock.patch('SoftLayer.FileStorageManager.get_volume_snapshot_notification_status') + def test_snapshot_get_notification_status(self, status): + status.side_effect = [None, 1, 0] + expected = ['Enabled', 'Enabled', 'Disabled'] + + for expect in expected: + result = self.run_command(['file', 'snapshot-get-notification-status', '999']) + self.assert_no_fail(result) + self.assertIn(expect, result.output) diff --git a/tests/managers/storage_generic_tests.py b/tests/managers/storage_generic_tests.py new file mode 100644 index 000000000..6585bd721 --- /dev/null +++ b/tests/managers/storage_generic_tests.py @@ -0,0 +1,35 @@ +""" + SoftLayer.tests.managers.storage_generic_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +import copy +import SoftLayer +from SoftLayer import exceptions +from SoftLayer import testing + + +class StorageGenericTests(testing.TestCase): + def set_up(self): + self.storage = SoftLayer.managers.storage.StorageManager(self.client) + + def test_get_volume_snapshot_notification_status(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'getSnapshotNotificationStatus') + # These are the values we expect from the API as of 2021-12-01, FBLOCK4193 + mock.side_effect = [None, '1', '0'] + expected = [1, 1, 0] + + for expect in expected: + result = self.storage.get_volume_snapshot_notification_status(12345) + self.assert_called_with('SoftLayer_Network_Storage', 'getSnapshotNotificationStatus', identifier=12345) + self.assertEqual(expect, result) + + def test_set_volume_snapshot_notification(self): + mock = self.set_mock('SoftLayer_Network_Storage', 'setSnapshotNotification') + mock.return_value = None + + result = self.storage.set_volume_snapshot_notification(12345, False) + self.assert_called_with('SoftLayer_Network_Storage', 'setSnapshotNotification', + identifier=12345, args=(False,)) From 1545608dd5fae7dda11438f78a2d68b033f54d2b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:34:44 -0600 Subject: [PATCH 0236/1050] fixed tox issues --- SoftLayer/managers/storage.py | 2 +- tests/managers/storage_generic_tests.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 980736876..f666a8222 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -25,7 +25,7 @@ def __init__(self, client): self.client = client self.resolvers = [self._get_ids_from_username] - def _get_ids_from_username(self, username): + def _get_ids_from_username(self, username): # pylint: disable=unused-argument,no-self-use """Should only be actually called from the block/file manager""" return [] diff --git a/tests/managers/storage_generic_tests.py b/tests/managers/storage_generic_tests.py index 6585bd721..1658ff0cf 100644 --- a/tests/managers/storage_generic_tests.py +++ b/tests/managers/storage_generic_tests.py @@ -5,9 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import copy import SoftLayer -from SoftLayer import exceptions from SoftLayer import testing @@ -33,3 +31,4 @@ def test_set_volume_snapshot_notification(self): result = self.storage.set_volume_snapshot_notification(12345, False) self.assert_called_with('SoftLayer_Network_Storage', 'setSnapshotNotification', identifier=12345, args=(False,)) + self.assertEqual(None, result) From 552784ea906ca71847b8074849ad3851d78e8b69 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 3 Dec 2021 16:50:20 -0600 Subject: [PATCH 0237/1050] tox fixes --- SoftLayer/CLI/block/count.py | 2 +- SoftLayer/CLI/file/count.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index ecfba0a53..cbd3d23b9 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -30,7 +30,7 @@ def cli(env, sortby, datacenter): service_resource = volume['serviceResource'] if 'datacenter' in service_resource: datacenter_name = service_resource['datacenter']['name'] - if datacenter_name not in datacenters.keys(): + if datacenter_name not in datacenters.keys(): # pylint: disable=consider-iterating-dictionary datacenters[datacenter_name] = 1 else: datacenters[datacenter_name] += 1 diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index cb6ed1a0a..325758538 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -29,7 +29,7 @@ def cli(env, sortby, datacenter): service_resource = volume['serviceResource'] if 'datacenter' in service_resource: datacenter_name = service_resource['datacenter']['name'] - if datacenter_name not in datacenters.keys(): + if datacenter_name not in datacenters.keys(): # pylint: disable=consider-iterating-dictionary datacenters[datacenter_name] = 1 else: datacenters[datacenter_name] += 1 From cb5f2f91cb070b992ac5550af3abe7c55c0d2197 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Dec 2021 15:01:12 -0600 Subject: [PATCH 0238/1050] Version and changelog update to 5.9.8 --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0eb2848..21dd2223b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Change Log +## [5.9.8] - 2021-12-07 + +https://github.com/softlayer/softlayer-python/compare/v5.9.7...v5.9.8 + +#### Improvements + +- Fix code blocks formatting of The Solution section docs #1534 +- Add retry decorator to documentation #1535 +- Updated utility docs #1536 +- Add Exceptions to Documentation #1537 +- Forces specific encoding on XMLRPC requests #1543 +- Add sensor data to hardware #1544 +- Ignoring f-string related messages for tox for now #1548 +- Fix account events #1546 +- Improved loadbal details #1549 +- Fix initialized accountmanger #1552 +- Fix hw billing reports 0 items #1556 +- Update API docs link and remove travisCI mention #1557 +- Fix errors with vs bandwidth #1563 +- Add Item names to vs billing report #1564 +- Mapping is now in collections.abc #1565 +- fix vs placementgroup list #1567 +- fixed up snapshot-notification cli commands #1569 + +#### New Commands +- loadbal l7policies #1553 + + ` slcli loadbal l7policies --protocol-id` + + `slcli loadbal l7policies` +- Snapshot notify #1554 + + `slcli file|block snapshot-set-notification` + + `slcli file|block snapshot-get-notification-status` + + + ## [5.9.7] - 2021-08-04 https://github.com/softlayer/softlayer-python/compare/v5.9.6...v5.9.7 @@ -173,7 +207,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 - #1318 add Drive number in guest drives details using the device number - #1323 add vs list hardware and all option -## [5.8.9] - 2020-07-06 +## [5.8.9] - 2020-07-06 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 - #1252 Automated Snap publisher diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 6d55ed2df..9f6d4b6da 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.7' +VERSION = 'v5.9.8' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 4eec2cad5..165223b88 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.7', + version='5.9.8', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From dc4a43e87548730d9c83b3ccb1f608814e9e8fbb Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 08:58:57 -0400 Subject: [PATCH 0239/1050] add new feature on vlan --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/create_options.py | 33 +++++++++++++++++++ .../fixtures/SoftLayer_Location_Datacenter.py | 2 ++ SoftLayer/managers/network.py | 14 ++++++++ tests/CLI/modules/vlan_tests.py | 4 +++ 5 files changed, 54 insertions(+) create mode 100644 SoftLayer/CLI/vlan/create_options.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d44a824f..2dbb519e6 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -354,6 +354,7 @@ ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), + ('vlan:create-options', 'SoftLayer.CLI.vlan.create_options:cli'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), diff --git a/SoftLayer/CLI/vlan/create_options.py b/SoftLayer/CLI/vlan/create_options.py new file mode 100644 index 000000000..5e79e1774 --- /dev/null +++ b/SoftLayer/CLI/vlan/create_options.py @@ -0,0 +1,33 @@ +"""Vlan order options.""" +# :license: MIT, see LICENSE for more details. +# pylint: disable=too-many-statements +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(short_help="Get options to use for creating Vlan servers.") +@environment.pass_env +def cli(env): + """Vlan order options.""" + + mgr = SoftLayer.NetworkManager(env.client) + datacenters = mgr.get_list_datacenter() + + table = formatting.Table(['name', 'Value'], title="Datacenters") + router_table = formatting.Table(['datacenter', 'hostname']) + dc_table = formatting.Table(['Datacenters']) + table.add_row(['VLAN type', 'Private, Public']) + + for datacenter in datacenters: + dc_table.add_row([datacenter['name']]) + routers = mgr.get_routers(datacenter['id']) + for router in routers: + router_table.add_row([datacenter['name'], router['hostname']]) + + table.add_row(['Datacenters', dc_table]) + table.add_row(['Routers', router_table]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py index e9aa9b48e..b0b937cf4 100644 --- a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -10,3 +10,5 @@ "name": "dal09" } ] + +getHardwareRouters = [] diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4bdb1c18a..eb648f518 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -789,3 +789,17 @@ def get_pods(self, datacenter=None): _filter = {"datacenterName": {"operation": datacenter}} return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) + + def get_list_datacenter(self): + """Calls SoftLayer_Location::getDatacenters() + + returns all datacenter locations. + """ + return self.client.call('SoftLayer_Location_Datacenter', 'getDatacenters') + + def get_routers(self, identifier): + """Calls SoftLayer_Location::getRouters() + + returns all routers locations. + """ + return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 204788d4d..6c8fbce01 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -18,6 +18,10 @@ def test_detail(self): result = self.run_command(['vlan', 'detail', '1234']) self.assert_no_fail(result) + def test_create_options(self): + result = self.run_command(['vlan', 'create-options']) + self.assert_no_fail(result) + def test_detail_no_vs(self): result = self.run_command(['vlan', 'detail', '1234', '--no-vs']) self.assert_no_fail(result) From 746ca96a3c18b599b8a165bf8c5a068d5c9ed42e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 09:08:19 -0400 Subject: [PATCH 0240/1050] add documentation --- docs/cli/vlan.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 72c9a54cf..6a9927ca1 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,6 +7,10 @@ VLANs :prog: vlan create :show-nested: +.. click:: SoftLayer.CLI.vlan.create-options:cli + :prog: vlan create-options + :show-nested: + .. click:: SoftLayer.CLI.vlan.detail:cli :prog: vlan detail :show-nested: From 666a86c254ff87cb4ae51b2837ed497d93a52db3 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 31 Dec 2021 09:14:26 -0400 Subject: [PATCH 0241/1050] add documentation --- docs/cli/vlan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6a9927ca1..be4890ce3 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,7 +7,7 @@ VLANs :prog: vlan create :show-nested: -.. click:: SoftLayer.CLI.vlan.create-options:cli +.. click:: SoftLayer.CLI.vlan.create_options:cli :prog: vlan create-options :show-nested: From 00b7d8187de80858e7170e3a1705c9184afb7982 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 7 Jan 2022 17:57:34 -0400 Subject: [PATCH 0242/1050] fix the team code review comments --- SoftLayer/CLI/vlan/create_options.py | 6 +++--- SoftLayer/managers/network.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/create_options.py b/SoftLayer/CLI/vlan/create_options.py index 5e79e1774..0b03b06af 100644 --- a/SoftLayer/CLI/vlan/create_options.py +++ b/SoftLayer/CLI/vlan/create_options.py @@ -11,13 +11,13 @@ @click.command(short_help="Get options to use for creating Vlan servers.") @environment.pass_env def cli(env): - """Vlan order options.""" + """List all the options for creating VLAN""" mgr = SoftLayer.NetworkManager(env.client) datacenters = mgr.get_list_datacenter() - table = formatting.Table(['name', 'Value'], title="Datacenters") - router_table = formatting.Table(['datacenter', 'hostname']) + table = formatting.Table(['Options', 'Value'], title="Datacenters") + router_table = formatting.Table(['Datacenter', 'Router/Pod']) dc_table = formatting.Table(['Datacenters']) table.add_row(['VLAN type', 'Private, Public']) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index eb648f518..6638a29d3 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -800,6 +800,6 @@ def get_list_datacenter(self): def get_routers(self, identifier): """Calls SoftLayer_Location::getRouters() - returns all routers locations. - """ + returns all routers locations. + """ return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) From 994b5c7fe863fbd25d27fcaf78389703c426e5f1 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 21 Jan 2022 18:04:43 -0400 Subject: [PATCH 0243/1050] Add loadbalancer timeout values --- SoftLayer/CLI/loadbal/detail.py | 38 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 6712c3ce2..ef850e029 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -39,17 +39,29 @@ def lbaas_table(this_lb): listener_table, pools = get_listener_table(this_lb) table.add_row(['Protocols', listener_table]) - member_table = get_member_table(this_lb, pools) - table.add_row(['Members', member_table]) - - hp_table = get_hp_table(this_lb) - table.add_row(['Health Checks', hp_table]) - - l7pool_table = get_l7pool_table(this_lb) - table.add_row(['L7 Pools', l7pool_table]) - - ssl_table = get_ssl_table(this_lb) - table.add_row(['Ciphers', ssl_table]) + if pools.get('members') is not None: + member_table = get_member_table(this_lb, pools) + table.add_row(['Members', member_table]) + else: + table.add_row(['Members', "Not Found"]) + + if this_lb.get('healthMonitors') != []: + hp_table = get_hp_table(this_lb) + table.add_row(['Health Checks', hp_table]) + else: + table.add_row(['Health Checks', "Not Found"]) + + if this_lb.get('l7Pools') != []: + l7pool_table = get_l7pool_table(this_lb) + table.add_row(['L7 Pools', l7pool_table]) + else: + table.add_row(['L7 Pools', "Not Found"]) + + if this_lb.get('sslCiphers') != []: + ssl_table = get_ssl_table(this_lb) + table.add_row(['Ciphers', ssl_table]) + else: + table.add_row(['Ciphers', "Not Found"]) return table @@ -57,14 +69,14 @@ def lbaas_table(this_lb): def get_hp_table(this_lb): """Generates a table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ - hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) + hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'ServerTimeout ', 'Modify', 'Active']) for health in this_lb.get('healthMonitors', []): hp_table.add_row([ health.get('uuid'), health.get('interval'), health.get('maxRetries'), health.get('monitorType'), - health.get('timeout'), + health.get('serverTimeout'), utils.clean_time(health.get('modifyDate')), health.get('provisioningStatus') ]) From c93fa575b2d6be8d8b7faf4cc84d057b1390d46d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 24 Jan 2022 14:27:25 -0600 Subject: [PATCH 0244/1050] #1575 added account bandwidth-pools and updated reports bandwidth to support the --pool option --- SoftLayer/CLI/account/bandwidth_pools.py | 43 +++++++++++++++++ SoftLayer/CLI/report/bandwidth.py | 60 ++++++++++++------------ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/account.py | 20 ++++++++ 4 files changed, 93 insertions(+), 31 deletions(-) create mode 100644 SoftLayer/CLI/account/bandwidth_pools.py diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py new file mode 100644 index 000000000..29dc9492a --- /dev/null +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -0,0 +1,43 @@ +"""Displays information about the accounts bandwidth pools""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + +from pprint import pprint as pp + +@click.command() +@environment.pass_env +def cli(env): + """Lists billing items with some other useful information. + + Similiar to https://cloud.ibm.com/billing/billing-items + """ + + manager = AccountManager(env.client) + items = manager.get_bandwidth_pools() + # table = item_table(items) + pp(items) + table = formatting.Table([ + "Pool Name", + "Region", + "Servers", + "Allocation", + "Current Usage", + "Projected Usage" + ], title="Bandwidth Pools") + table.align = 'l' + + for item in items: + name = item.get('name') + region = utils.lookup(item, 'locationGroup', 'name') + servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) + allocation = item.get('totalBandwidthAllocated', 0) + current = item.get('billingCyclePublicUsageTotal', 0) + projected = item.get('projectedPublicBandwidthUsage', 0) + + table.add_row([name, region, servers, allocation, current, projected,]) + env.fout(table) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 4ae2d0f68..9ada68e14 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -9,6 +9,7 @@ from SoftLayer.CLI import formatting from SoftLayer import utils +from pprint import pprint as pp # pylint: disable=unused-argument def _validate_datetime(ctx, param, value): @@ -47,23 +48,25 @@ def _get_pooled_bandwidth(env, start, end): label='Calculating for bandwidth pools', file=sys.stderr) as pools: for pool in pools: - if not pool.get('metricTrackingObjectId'): - continue - - yield { - 'id': pool['id'], + pool_detail = { + 'id': pool.get('id'), 'type': 'pool', - 'name': pool['name'], - 'data': env.client.call( + 'name': pool.get('name'), + 'data': [] + } + if pool.get('metricTrackingObjectId'): + bw_data = env.client.call( 'Metric_Tracking_Object', 'getSummaryData', start.strftime('%Y-%m-%d %H:%M:%S %Z'), end.strftime('%Y-%m-%d %H:%M:%S %Z'), types, 300, - id=pool['metricTrackingObjectId'], - ), - } + id=pool.get('metricTrackingObjectId'), + ) + pool_detail['data'] = bw_data + + yield pool_detail def _get_hardware_bandwidth(env, start, end): @@ -172,28 +175,20 @@ def _get_virtual_bandwidth(env, start, end): @click.command(short_help="Bandwidth report for every pool/server") -@click.option( - '--start', - callback=_validate_datetime, - default=(datetime.datetime.now() - datetime.timedelta(days=30) - ).strftime('%Y-%m-%d'), - help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") -@click.option( - '--end', - callback=_validate_datetime, - default=datetime.datetime.now().strftime('%Y-%m-%d'), - help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") -@click.option('--sortby', help='Column to sort by', - default='hostname', - show_default=True) -@click.option('--virtual', is_flag=True, - help='Show the all bandwidth summary for each virtual server', - default=False) -@click.option('--server', is_flag=True, - help='show the all bandwidth summary for each hardware server', - default=False) +@click.option('--start', callback=_validate_datetime, + default=(datetime.datetime.now() - datetime.timedelta(days=30)).strftime('%Y-%m-%d'), + help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") +@click.option('--end', callback=_validate_datetime, default=datetime.datetime.now().strftime('%Y-%m-%d'), + help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") +@click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) +@click.option('--virtual', is_flag=True, default=False, + help='Show only the bandwidth summary for each virtual server') +@click.option('--server', is_flag=True, default=False, + help='Show only the bandwidth summary for each hardware server') +@click.option('--pool', is_flag=True, default=False, + help='Show only the bandwidth pool summary.') @environment.pass_env -def cli(env, start, end, sortby, virtual, server): +def cli(env, start, end, sortby, virtual, server, pool): """Bandwidth report for every pool/server. This reports on the total data transfered for each virtual sever, hardware @@ -243,6 +238,9 @@ def _input_to_table(item): for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end)): _input_to_table(item) + elif pool: + for item in _get_pooled_bandwidth(env, start, end): + _input_to_table(item) else: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end), diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2dbb519e6..02d3420d3 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -22,6 +22,7 @@ ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), ('account:cancel-item', 'SoftLayer.CLI.account.cancel_item:cli'), ('account:orders', 'SoftLayer.CLI.account.orders:cli'), + ('account:bandwidth-pools', 'SoftLayer.CLI.account.bandwidth_pools:cli'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 51c9c889c..9307ea1d5 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -326,3 +326,23 @@ def get_active_account_licenses(self): _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) + + def get_bandwidth_pools(self, mask=None): + """Gets all the bandwidth pools on an account""" + + if mask is None: + mask = """mask[totalBandwidthAllocated,locationGroup, id, name, billingCyclePublicUsageTotal, + projectedPublicBandwidthUsage] + """ + + return self.client.call('SoftLayer_Account', 'getBandwidthAllotments', mask=mask, iter=True) + + def get_bandwidth_pool_counts(self, identifier): + """Gets a count of all servers in a bandwidth pool""" + mask = "mask[id, bareMetalInstanceCount, hardwareCount, virtualGuestCount]" + counts = self.client.call('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', + id=identifier, mask=mask) + total = counts.get('bareMetalInstanceCount', 0) + \ + counts.get('hardwareCount', 0) + \ + counts.get('virtualGuestCount', 0) + return total \ No newline at end of file From 2e7ac4ae1ec66b08260739f63b002e6e656f8cc2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 Jan 2022 14:35:27 -0600 Subject: [PATCH 0245/1050] #1575 fixed some style and output issues --- SoftLayer/CLI/account/bandwidth_pools.py | 16 +++++++--------- SoftLayer/CLI/report/bandwidth.py | 1 - SoftLayer/managers/account.py | 16 ++++++++++------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index 29dc9492a..358bfd4d0 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -7,20 +7,18 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils -from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): - """Lists billing items with some other useful information. + """Displays bandwidth pool information - Similiar to https://cloud.ibm.com/billing/billing-items + Similiar to https://cloud.ibm.com/classic/network/bandwidth/vdr """ manager = AccountManager(env.client) items = manager.get_bandwidth_pools() - # table = item_table(items) - pp(items) + table = formatting.Table([ "Pool Name", "Region", @@ -35,9 +33,9 @@ def cli(env): name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) - allocation = item.get('totalBandwidthAllocated', 0) - current = item.get('billingCyclePublicUsageTotal', 0) - projected = item.get('projectedPublicBandwidthUsage', 0) + allocation = "{} GB".format(item.get('totalBandwidthAllocated', 0)) + current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) + projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([name, region, servers, allocation, current, projected,]) + table.add_row([name, region, servers, allocation, current, projected]) env.fout(table) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 9ada68e14..50b002304 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -9,7 +9,6 @@ from SoftLayer.CLI import formatting from SoftLayer import utils -from pprint import pprint as pp # pylint: disable=unused-argument def _validate_datetime(ctx, param, value): diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 9307ea1d5..4e6a3a26a 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -331,18 +331,22 @@ def get_bandwidth_pools(self, mask=None): """Gets all the bandwidth pools on an account""" if mask is None: - mask = """mask[totalBandwidthAllocated,locationGroup, id, name, billingCyclePublicUsageTotal, - projectedPublicBandwidthUsage] + mask = """mask[totalBandwidthAllocated,locationGroup, id, name, projectedPublicBandwidthUsage, + billingCyclePublicBandwidthUsage[amountOut,amountIn]] """ return self.client.call('SoftLayer_Account', 'getBandwidthAllotments', mask=mask, iter=True) def get_bandwidth_pool_counts(self, identifier): - """Gets a count of all servers in a bandwidth pool""" + """Gets a count of all servers in a bandwidth pool + + Getting the server counts individually is significantly faster than pulling them in + with the get_bandwidth_pools api call. + """ mask = "mask[id, bareMetalInstanceCount, hardwareCount, virtualGuestCount]" counts = self.client.call('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', id=identifier, mask=mask) total = counts.get('bareMetalInstanceCount', 0) + \ - counts.get('hardwareCount', 0) + \ - counts.get('virtualGuestCount', 0) - return total \ No newline at end of file + counts.get('hardwareCount', 0) + \ + counts.get('virtualGuestCount', 0) + return total From 25fac1496d0d9a7c859dba28a0e10dd6208a94c3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 26 Jan 2022 09:38:24 -0400 Subject: [PATCH 0246/1050] Add pricing date to slcli order preset-list --- SoftLayer/CLI/order/preset_list.py | 45 +++++++++++++++---- .../fixtures/SoftLayer_Product_Package.py | 33 ++++++++++++-- SoftLayer/managers/ordering.py | 2 +- tests/CLI/modules/order_tests.py | 5 +++ 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index 412d95ee7..d40d42093 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -16,8 +16,9 @@ @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter preset names.") +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, e.g. --prices') @environment.pass_env -def cli(env, package_keyname, keyword): +def cli(env, package_keyname, keyword, prices): """List package presets. .. Note:: @@ -33,6 +34,8 @@ def cli(env, package_keyname, keyword): slcli order preset-list BARE_METAL_SERVER --keyword gpu """ + + tables = [] table = formatting.Table(COLUMNS) manager = ordering.OrderingManager(env.client) @@ -41,10 +44,36 @@ def cli(env, package_keyname, keyword): _filter = {'activePresets': {'name': {'operation': '*= %s' % keyword}}} presets = manager.list_presets(package_keyname, filter=_filter) - for preset in presets: - table.add_row([ - str(preset['name']).strip(), - str(preset['keyName']).strip(), - str(preset['description']).strip() - ]) - env.fout(table) + if prices: + table_prices = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction', 'Location']) + for price in presets: + locations = [] + if price['locations'] != []: + for location in price['locations']: + locations.append(location['name']) + cr_max = get_item_price_data(price['prices'][0], 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price['prices'][0], 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price['prices'][0], 'capacityRestrictionType') + table_prices.add_row([price['keyName'], price['id'], + get_item_price_data(price['prices'][0], 'hourlyRecurringFee'), + get_item_price_data(price['prices'][0], 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type), str(locations)]) + tables.append(table_prices) + + else: + for preset in presets: + table.add_row([ + str(preset['name']).strip(), + str(preset['keyName']).strip(), + str(preset['description']).strip() + ]) + tables.append(table) + env.fout(tables) + + +def get_item_price_data(price, item_attribute): + """Given an SoftLayer_Product_Item_Price, returns its default price data""" + result = '-' + if item_attribute in price: + result = price[item_attribute] + return result diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 95fa34ed2..c4c45985d 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1847,7 +1847,16 @@ "isActive": "1", "keyName": "M1_64X512X25", "name": "M1.64x512x25", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 258963, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] }, { "description": "M1.56x448x100", @@ -1855,7 +1864,16 @@ "isActive": "1", "keyName": "M1_56X448X100", "name": "M1.56x448x100", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 698563, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] }, { "description": "M1.64x512x100", @@ -1863,7 +1881,16 @@ "isActive": "1", "keyName": "M1_64X512X100", "name": "M1.64x512x100", - "packageId": 835 + "packageId": 835, + "locations": [], + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 963258, + "itemId": 8195, + "recurringFee": "0", + "setupFee": "0" + }] } ] diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 18513e1e4..2bb7bae0a 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -17,7 +17,7 @@ PACKAGE_MASK = '''id, name, keyName, isActive, type''' -PRESET_MASK = '''id, name, keyName, description''' +PRESET_MASK = '''id, name, keyName, description, categories, prices, locations''' class OrderingManager(object): diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 24495d0ff..0e8878093 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -300,6 +300,11 @@ def test_preset_list_keywork(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets', filter=_filter) + def test_preset_list_prices(self): + result = self.run_command(['order', 'preset-list', 'package', '--prices']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets') + def test_location_list(self): result = self.run_command(['order', 'package-locations', 'package']) self.assert_no_fail(result) From ad4186c0164eaa519d0c662a57b62476be26400c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Jan 2022 13:37:54 -0600 Subject: [PATCH 0247/1050] #1575 unit tests for bandwidth pooling code --- SoftLayer/fixtures/SoftLayer_Account.py | 18 ++ ...er_Network_Bandwidth_Version1_Allotment.py | 6 + docs/cli/account.rst | 4 + tests/CLI/modules/account_tests.py | 6 + tests/CLI/modules/report_tests.py | 233 ++++-------------- tests/managers/account_tests.py | 10 + 6 files changed, 86 insertions(+), 191 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 35216be76..3f7d3cf4c 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1207,3 +1207,21 @@ "version": 4 } }] + +getBandwidthAllotments = [{ + 'billingCyclePublicBandwidthUsage': { + 'amountIn': '6.94517', + 'amountOut': '6.8859' + }, + 'id': 309961, + 'locationGroup': { + 'description': 'All Datacenters in Mexico', + 'id': 262, + 'locationGroupTypeId': 1, + 'name': 'MEX', + 'securityLevelId': None + }, + 'name': 'MexRegion', + 'projectedPublicBandwidthUsage': 9.88, + 'totalBandwidthAllocated': 3361 +}] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py new file mode 100644 index 000000000..d784b7e7b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -0,0 +1,6 @@ +getObject = { + 'id': 309961, + 'bareMetalInstanceCount': 0, + 'hardwareCount': 2, + 'virtualGuestCount': 0 +} \ No newline at end of file diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 719c44fde..8cb855f13 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -43,3 +43,7 @@ Account Commands .. click:: SoftLayer.CLI.account.licenses:cli :prog: account licenses :show-nested: + +.. click:: SoftLayer.CLI.account.bandwidth_pools:cli + :prog: account bandwidth-pools + :show-nested: diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 8428d3306..9ac821ace 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -136,3 +136,9 @@ def test_acccount_licenses(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') + + def test_bandwidth_pools(self): + result = self.run_command(['account', 'bandwidth-pools']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments') + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') \ No newline at end of file diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 8489aeeab..11c7448e7 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -8,6 +8,7 @@ import json +from pprint import pprint as pp class ReportTests(testing.TestCase): @@ -76,8 +77,7 @@ def test_bandwidth_report(self): 'hostname': 'host3', 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, }] - summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', - 'getSummaryData') + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', 'getSummaryData') summary_data.return_value = [ {'type': 'publicIn_net_octet', 'counter': 10}, {'type': 'publicOut_net_octet', 'counter': 20}, @@ -93,86 +93,23 @@ def test_bandwidth_report(self): ]) self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool' - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool' - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware' - }, { - 'hostname': 'host3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware' - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual' - }, { - 'hostname': 'host3', - 'pool': 2, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual'}], - json.loads(stripped_output), - ) - self.assertEqual( - 6, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=1) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=3) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=101) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=103) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=201) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=203) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + json_output = json.loads(stripped_output) + pp(json.loads(stripped_output)) + print("======= ^^^^^^^^^ ==============") + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[0]['private_in'], 30) + + self.assertEqual(6, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', @@ -242,64 +179,19 @@ def test_virtual_bandwidth_report(self): self.assert_no_fail(result) stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual', - }, { - 'hostname': 'host3', - 'pool': 2, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'virtual', - }], - json.loads(stripped_output), - ) - self.assertEqual( - 4, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=1) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=3) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=201) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=203) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + json_output = json.loads(stripped_output) + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[1]['private_in'], 0) + self.assertEqual(json_output[2]['private_in'], 30) + self.assertEqual(json_output[3]['type'], 'virtual') + + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', @@ -370,59 +262,18 @@ def test_server_bandwidth_report(self): self.assert_no_fail(result) stripped_output = '[' + result.output.split('[', 1)[1] - self.assertEqual([ - { - 'hostname': 'pool1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'pool3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'pool', - }, { - 'hostname': 'host1', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware', - }, { - 'hostname': 'host3', - 'pool': None, - 'private_in': 30, - 'private_out': 40, - 'public_in': 10, - 'public_out': 20, - 'type': 'hardware', - }, ], - json.loads(stripped_output), - ) - self.assertEqual( - 4, - len(self.calls('SoftLayer_Metric_Tracking_Object', - 'getSummaryData')), - ) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=101) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', - 'getSummaryData', - identifier=103) + json_output = json.loads(stripped_output) + self.assertEqual(json_output[0]['hostname'], 'pool1') + self.assertEqual(json_output[1]['private_in'], 0) + self.assertEqual(json_output[2]['private_in'], 30) + self.assertEqual(json_output[3]['type'], 'hardware') + + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) - call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', - identifier=1)[0] - expected_args = ( - '2016-02-04 00:00:00 ', - '2016-03-04 12:34:56 ', + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] + expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', [{ 'keyName': 'PUBLICIN', 'name': 'publicIn', diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 6d515de38..11380e370 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -3,6 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ """ +from unittest import mock as mock from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import SoftLayerAPIError @@ -166,3 +167,12 @@ def test_get_routers_with_datacenter(self): self.manager.get_routers(location='dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) + + def test_get_bandwidth_pools(self): + self.manager.get_bandwidth_pools() + self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments', mask=mock.ANY) + + def test_get_bandwidth_pool_counts(self): + total = self.manager.get_bandwidth_pool_counts(1234) + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', identifier=1234) + self.assertEqual(total, 2) \ No newline at end of file From 2665d367fd5ee567fed7a6fa92439481c39a2fc3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Jan 2022 16:06:15 -0600 Subject: [PATCH 0248/1050] tox fixes --- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- ...er_Network_Bandwidth_Version1_Allotment.py | 2 +- tests/CLI/modules/account_tests.py | 2 +- tests/CLI/modules/report_tests.py | 123 +++++++++--------- tests/managers/account_tests.py | 2 +- 5 files changed, 66 insertions(+), 65 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 3f7d3cf4c..fb5aedb67 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1224,4 +1224,4 @@ 'name': 'MexRegion', 'projectedPublicBandwidthUsage': 9.88, 'totalBandwidthAllocated': 3361 -}] \ No newline at end of file +}] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py index d784b7e7b..422e7721d 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -3,4 +3,4 @@ 'bareMetalInstanceCount': 0, 'hardwareCount': 2, 'virtualGuestCount': 0 -} \ No newline at end of file +} diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 9ac821ace..b33c38c6e 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -141,4 +141,4 @@ def test_bandwidth_pools(self): result = self.run_command(['account', 'bandwidth-pools']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getBandwidthAllotments') - self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') \ No newline at end of file + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 11c7448e7..f756704c0 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -8,7 +8,8 @@ import json -from pprint import pprint as pp +from pprint import pprint as pp + class ReportTests(testing.TestCase): @@ -93,14 +94,14 @@ def test_bandwidth_report(self): ]) self.assert_no_fail(result) - + stripped_output = '[' + result.output.split('[', 1)[1] json_output = json.loads(stripped_output) pp(json.loads(stripped_output)) print("======= ^^^^^^^^^ ==============") self.assertEqual(json_output[0]['hostname'], 'pool1') self.assertEqual(json_output[0]['private_in'], 30) - + self.assertEqual(6, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=3) @@ -110,25 +111,25 @@ def test_bandwidth_report(self): self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) def test_virtual_bandwidth_report(self): @@ -192,25 +193,25 @@ def test_virtual_bandwidth_report(self): self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=203) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) def test_server_bandwidth_report(self): @@ -267,30 +268,30 @@ def test_server_bandwidth_report(self): self.assertEqual(json_output[1]['private_in'], 0) self.assertEqual(json_output[2]['private_in'], 30) self.assertEqual(json_output[3]['type'], 'hardware') - + self.assertEqual(4, len(self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData'))) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=101) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=103) call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', identifier=1)[0] expected_args = ('2016-02-04 00:00:00 ', '2016-03-04 12:34:56 ', - [{ - 'keyName': 'PUBLICIN', - 'name': 'publicIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PUBLICOUT', - 'name': 'publicOut', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEIN', - 'name': 'privateIn', - 'summaryType': 'sum', - }, { - 'keyName': 'PRIVATEOUT', - 'name': 'privateOut', - 'summaryType': 'sum', - }], - 300, - ) + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) self.assertEqual(expected_args, call.args) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 11380e370..b32c223f6 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -175,4 +175,4 @@ def test_get_bandwidth_pools(self): def test_get_bandwidth_pool_counts(self): total = self.manager.get_bandwidth_pool_counts(1234) self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject', identifier=1234) - self.assertEqual(total, 2) \ No newline at end of file + self.assertEqual(total, 2) From 8c5fd3c0ce5ff339feaf21b10f20def171614284 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 4 Feb 2022 15:26:17 -0600 Subject: [PATCH 0249/1050] v5.9.9 changelog and updates --- CHANGELOG.md | 13 +++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21dd2223b..7f9166bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log + +## [5.9.9] - 2021-12-07 + +https://github.com/softlayer/softlayer-python/compare/v5.9.8...v5.9.9 + +#### Improvements +- Add loadbalancer timeout values #1576 +- Add pricing date to slcli order preset-list #1578 + +#### New Commands +- `slcli vlan create-options` add new feature on vlan #1572 +- `slcli account bandwidth-pools` Bandwidth pool features #1579 + ## [5.9.8] - 2021-12-07 https://github.com/softlayer/softlayer-python/compare/v5.9.7...v5.9.8 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 9f6d4b6da..e7749589d 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.8' +VERSION = 'v5.9.9' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 165223b88..37af1f9b5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.8', + version='5.9.9', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From fd579dd13fe7f4814577979b00714abbd3d7b4b6 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Feb 2022 10:59:02 -0400 Subject: [PATCH 0250/1050] Bandwidth pool management --- .../CLI/account/bandwidth_pools_detail.py | 66 ++++++++ SoftLayer/CLI/routes.py | 1 + ...er_Network_Bandwidth_Version1_Allotment.py | 145 ++++++++++++++++++ SoftLayer/managers/account.py | 11 ++ docs/cli/account.rst | 4 + tests/CLI/modules/account_tests.py | 5 + 6 files changed, 232 insertions(+) create mode 100644 SoftLayer/CLI/account/bandwidth_pools_detail.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py new file mode 100644 index 000000000..1878acde5 --- /dev/null +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -0,0 +1,66 @@ +"""Get bandwidth pools.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer import AccountManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get bandwidth about a VLAN.""" + + manager = AccountManager(env.client) + bandwidths = manager.getBandwidthDetail(identifier) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', bandwidths['id']]) + table.add_row(['Name', bandwidths['name']]) + table.add_row(['Create Date', bandwidths['createDate']]) + table.add_row(['Current Usage', bandwidths['billingCyclePublicBandwidthUsage']['amountOut']]) + table.add_row(['Projected Usage', bandwidths['projectedPublicBandwidthUsage']]) + table.add_row(['Inbound Usage', bandwidths['inboundPublicBandwidthUsage']]) + if bandwidths['hardware'] != []: + table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) + else: + table.add_row(['hardware', 'not found']) + + if bandwidths['virtualGuests'] != []: + table.add_row(['virtualGuests', _virtual_table(bandwidths['virtualGuests'])]) + else: + table.add_row(['virtualGuests', 'Not Found']) + + if bandwidths['bareMetalInstances'] != []: + table.add_row(['Netscale', _bw_table(bandwidths['bareMetalInstances'])]) + else: + table.add_row(['Netscale', 'Not Found']) + + env.fout(table) + + +def _bw_table(bw_data): + """Generates a bandwidth useage table""" + table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) + for bw_point in bw_data: + amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] + current = utils.lookup(bw_point, 'outboundBandwidthUsage') + ip_address = bw_point['primaryIpAddress'] + table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) + return [table_data] + + +def _virtual_table(bw_data): + """Generates a virtual bandwidth usage table""" + table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) + for bw_point in bw_data: + amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] + current = utils.lookup(bw_point, 'outboundPublicBandwidthUsage') + ip_address = bw_point['primaryIpAddress'] + table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) + return [table_data] diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d44a824f..85a6ee336 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -15,6 +15,7 @@ ('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'), ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), ('account:events', 'SoftLayer.CLI.account.events:cli'), + ('account:bandwidth-pools-detail', 'SoftLayer.CLI.account.bandwidth_pools_detail:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), ('account:licenses', 'SoftLayer.CLI.account.licenses:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py new file mode 100644 index 000000000..ddf8752a3 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Bandwidth_Version1_Allotment.py @@ -0,0 +1,145 @@ +getObject = { + 'bandwidthAllotmentTypeId': 2, + 'createDate': '2016-07-25T08:31:17-07:00', + 'id': 123456, + 'locationGroupId': 262, + 'name': 'MexRegion', + 'serviceProviderId': 1, + 'activeDetails': [ + { + 'allocationId': 48293300, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882086, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 48293300, + } + }, + { + 'allocationId': 48293302, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882088, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 48293302, + } + } + ], + 'bareMetalInstances': [], + 'billingCyclePublicBandwidthUsage': { + 'amountIn': '.23642', + 'amountOut': '.05475', + 'bandwidthUsageDetailTypeId': '1', + 'trackingObject': { + 'id': 258963, + 'resourceTableId': 309961, + 'startDate': '2021-03-10T11:04:56-06:00', + } + }, + 'hardware': [ + { + 'domain': 'test.com', + 'fullyQualifiedDomainName': 'testpooling.test.com', + 'hardwareStatusId': 5, + 'hostname': 'testpooling', + 'id': 36589, + 'manufacturerSerialNumber': 'J122Y7N', + 'provisionDate': '2022-01-24T15:17:03-06:00', + 'serialNumber': 'SL018EA8', + 'serviceProviderId': 1, + 'bandwidthAllotmentDetail': { + 'allocationId': 48293302, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882088, + 'allocation': { + 'amount': '5000', + 'id': 48293302, + } + }, + 'globalIdentifier': '36e63026-5fa1-456d-a04f-adf34e60e2f4', + 'hardwareStatus': { + 'id': 5, + 'status': 'ACTIVE' + }, + 'networkManagementIpAddress': '10.130.97.247', + 'outboundBandwidthUsage': '.02594', + 'primaryBackendIpAddress': '10.130.97.227', + 'primaryIpAddress': '169.57.4.70', + 'privateIpAddress': '10.130.97.227' + }, + { + 'domain': 'testtest.com', + 'fullyQualifiedDomainName': 'testpooling2.test.com', + 'hardwareStatusId': 5, + 'hostname': 'testpooling2', + 'id': 25478, + 'manufacturerSerialNumber': 'J12935M', + 'notes': '', + 'provisionDate': '2022-01-24T15:44:20-06:00', + 'serialNumber': 'SL01HIIB', + 'serviceProviderId': 1, + 'bandwidthAllotmentDetail': { + 'allocationId': 48293300, + 'bandwidthAllotmentId': 309961, + 'effectiveDate': '2022-02-04T00:00:00-06:00', + 'id': 48882086, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '5000', + 'id': 478965, + } + }, + 'globalIdentifier': '6ea407bd-9c07-4129-9103-9fda8a9e7028', + 'hardwareStatus': { + 'id': 5, + 'status': 'ACTIVE' + }, + 'networkManagementIpAddress': '10.130.97.252', + 'outboundBandwidthUsage': '.02884', + 'primaryBackendIpAddress': '10.130.97.248', + 'primaryIpAddress': '169.57.4.73', + 'privateIpAddress': '10.130.97.248' + } + ], + 'inboundPublicBandwidthUsage': '.23642', + 'projectedPublicBandwidthUsage': 0.43, + 'virtualGuests': [{ + 'createDate': '2021-06-09T13:49:28-07:00', + 'deviceStatusId': 8, + 'domain': 'cgallo.com', + 'fullyQualifiedDomainName': 'KVM-Test.test.com', + 'hostname': 'KVM-Test', + 'id': 3578963, + 'maxCpu': 2, + 'maxCpuUnits': 'CORE', + 'maxMemory': 4096, + 'startCpus': 2, + 'statusId': 1001, + 'typeId': 1, + 'uuid': '15951561-6171-0dfc-f3d2-be039e51cc10', + 'bandwidthAllotmentDetail': { + 'allocationId': 45907006, + 'bandwidthAllotmentId': 138442, + 'effectiveDate': '2021-06-09T13:49:31-07:00', + 'id': 46467342, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '0', + 'id': 45907006, + } + }, + 'globalIdentifier': 'a245a7dd-acd1-4d1a-9356-cc1ac6b55b98', + 'outboundPublicBandwidthUsage': '.02845', + 'primaryBackendIpAddress': '10.208.73.53', + 'primaryIpAddress': '169.48.96.27', + 'status': { + 'keyName': 'ACTIVE', + 'name': 'Active' + } + }] +} diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 51c9c889c..905fc6cd0 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -326,3 +326,14 @@ def get_active_account_licenses(self): _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) + + def getBandwidthDetail(self, identifier): + """Gets bandwidth pool detail. + + :returns: bandwidth pool detail + """ + _mask = """activeDetails[allocation],projectedPublicBandwidthUsage, billingCyclePublicBandwidthUsage, + hardware[outboundBandwidthUsage,bandwidthAllotmentDetail[allocation]],inboundPublicBandwidthUsage, + virtualGuests[outboundPublicBandwidthUsage,bandwidthAllotmentDetail[allocation]], + bareMetalInstances[outboundBandwidthUsage,bandwidthAllotmentDetail[allocation]]""" + return self.client['SoftLayer_Network_Bandwidth_Version1_Allotment'].getObject(id=identifier, mask=_mask) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 719c44fde..e7a248baf 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -43,3 +43,7 @@ Account Commands .. click:: SoftLayer.CLI.account.licenses:cli :prog: account licenses :show-nested: + +.. click:: SoftLayer.CLI.account.bandwidth_pools_detail:cli + :prog: account bandwidth-pools-detail + :show-nested: diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 8428d3306..aca62218c 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -136,3 +136,8 @@ def test_acccount_licenses(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') + + def test_acccount_bandwidth_pool_detail(self): + result = self.run_command(['account', 'bandwidth-pools-detail', '123456']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Bandwidth_Version1_Allotment', 'getObject') From bc42a742a2615f1aba24f95350133b117f344d0f Mon Sep 17 00:00:00 2001 From: David Runge Date: Thu, 10 Feb 2022 00:18:42 +0100 Subject: [PATCH 0251/1050] Replace the use of ptable with prettytable {README.rst,tools/*}: Replace ptable with prettytable >= 2.0.0. SoftLayer/CLI/formatting.py: Only consider the import of prettytable. --- README.rst | 2 +- SoftLayer/CLI/formatting.py | 6 +----- setup.py | 2 +- tools/requirements.txt | 4 ++-- tools/test-requirements.txt | 4 ++-- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 15d5bcca3..3cb2219f8 100644 --- a/README.rst +++ b/README.rst @@ -167,7 +167,7 @@ If you cannot install python 3.6+ for some reason, you will need to use a versio Python Packages --------------- -* ptable >= 0.9.2 +* prettytable >= 2.0.0 * click >= 7 * requests >= 2.20.0 * prompt_toolkit >= 2 diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b28c54fe6..fcb5fe625 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -11,11 +11,7 @@ import click -# If both PTable and prettytable are installed, its impossible to use the new version -try: - from prettytable import prettytable -except ImportError: - import prettytable +import prettytable from SoftLayer.CLI import exceptions from SoftLayer import utils diff --git a/setup.py b/setup.py index 37af1f9b5..945dcf95b 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ }, python_requires='>=3.5', install_requires=[ - 'ptable >= 0.9.2', + 'prettytable >= 2.0.0', 'click >= 7', 'requests >= 2.20.0', 'prompt_toolkit >= 2', diff --git a/tools/requirements.txt b/tools/requirements.txt index ad902bc39..09f985d84 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,6 +1,6 @@ -ptable >= 0.9.2 +prettytable >= 2.0.0 click >= 7 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 -urllib3 >= 1.24 \ No newline at end of file +urllib3 >= 1.24 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 0f1ec684c..3cc0f32e6 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,9 +4,9 @@ pytest pytest-cov mock sphinx -ptable >= 0.9.2 +prettytable >= 2.0.0 click >= 7 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 -urllib3 >= 1.24 \ No newline at end of file +urllib3 >= 1.24 From eee36d53381d34c427c81f050b06c5e0664c70e5 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 08:59:37 -0400 Subject: [PATCH 0252/1050] fix the some problems and fix the team code review comments --- .../CLI/account/bandwidth_pools_detail.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index 1878acde5..e4303c99d 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -12,7 +12,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get bandwidth about a VLAN.""" + """Get bandwidth pool details.""" manager = AccountManager(env.client) bandwidths = manager.getBandwidthDetail(identifier) @@ -23,9 +23,12 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - table.add_row(['Current Usage', bandwidths['billingCyclePublicBandwidthUsage']['amountOut']]) - table.add_row(['Projected Usage', bandwidths['projectedPublicBandwidthUsage']]) - table.add_row(['Inbound Usage', bandwidths['inboundPublicBandwidthUsage']]) + current = "{} GB".format(bandwidths['billingCyclePublicBandwidthUsage']['amountOut'], 0) + table.add_row(['Current Usage', current]) + projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) + table.add_row(['Projected Usage', projected]) + inbound = "{} GB".format(bandwidths.get('inboundPublicBandwidthUsage', 0)) + table.add_row(['Inbound Usage', inbound]) if bandwidths['hardware'] != []: table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) else: @@ -48,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] - current = utils.lookup(bw_point, 'outboundBandwidthUsage') + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -59,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = bw_point['bandwidthAllotmentDetail']['allocation']['amount'] - current = utils.lookup(bw_point, 'outboundPublicBandwidthUsage') + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 255ff24a7f6a038100ed5c17c353f07da9b2cdc0 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 10:38:23 -0400 Subject: [PATCH 0253/1050] fix tox tool --- SoftLayer/CLI/account/bandwidth_pools_detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index e4303c99d..4110a04df 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -23,7 +23,7 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - current = "{} GB".format(bandwidths['billingCyclePublicBandwidthUsage']['amountOut'], 0) + current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut', 0)) table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) table.add_row(['Projected Usage', projected]) @@ -51,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut'), 0) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage'), 0) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From fc5614c040cccff6411c87ad6cc191f7ec0b6971 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 11 Feb 2022 10:42:14 -0400 Subject: [PATCH 0254/1050] Add id in the result in the command bandwidth-pools --- SoftLayer/CLI/account/bandwidth_pools.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index 358bfd4d0..ddcfd0a86 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -20,6 +20,7 @@ def cli(env): items = manager.get_bandwidth_pools() table = formatting.Table([ + "Id", "Pool Name", "Region", "Servers", @@ -28,8 +29,8 @@ def cli(env): "Projected Usage" ], title="Bandwidth Pools") table.align = 'l' - for item in items: + id = item.get('id') name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) @@ -37,5 +38,5 @@ def cli(env): current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([name, region, servers, allocation, current, projected]) + table.add_row([id, name, region, servers, allocation, current, projected]) env.fout(table) From f03fdb5f4194b1e3b89c300a8c0ae75352a0c995 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 11 Feb 2022 10:52:26 -0400 Subject: [PATCH 0255/1050] id renamed to id_bandwidth --- SoftLayer/CLI/account/bandwidth_pools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools.py b/SoftLayer/CLI/account/bandwidth_pools.py index ddcfd0a86..2d94c7bbb 100644 --- a/SoftLayer/CLI/account/bandwidth_pools.py +++ b/SoftLayer/CLI/account/bandwidth_pools.py @@ -30,7 +30,7 @@ def cli(env): ], title="Bandwidth Pools") table.align = 'l' for item in items: - id = item.get('id') + id_bandwidth = item.get('id') name = item.get('name') region = utils.lookup(item, 'locationGroup', 'name') servers = manager.get_bandwidth_pool_counts(identifier=item.get('id')) @@ -38,5 +38,5 @@ def cli(env): current = "{} GB".format(utils.lookup(item, 'billingCyclePublicBandwidthUsage', 'amountOut')) projected = "{} GB".format(item.get('projectedPublicBandwidthUsage', 0)) - table.add_row([id, name, region, servers, allocation, current, projected]) + table.add_row([id_bandwidth, name, region, servers, allocation, current, projected]) env.fout(table) From 43b33de5f130dbd86daf6c468665b5e227bfe0a7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 11 Feb 2022 11:32:17 -0400 Subject: [PATCH 0256/1050] fix tox tool --- SoftLayer/CLI/account/bandwidth_pools_detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index 4110a04df..a555be065 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -23,7 +23,7 @@ def cli(env, identifier): table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) table.add_row(['Create Date', bandwidths['createDate']]) - current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut', 0)) + current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut')) table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) table.add_row(['Projected Usage', projected]) @@ -51,8 +51,8 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +62,8 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut', 0)) - current = "{} GB".format(utils.lookup(bw_point, 'outboundBandwidthUsage', 0)) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) ip_address = bw_point['primaryIpAddress'] table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 6f8590690b22d3e076a6f9b2baf4cf42656bf326 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Feb 2022 09:59:13 -0400 Subject: [PATCH 0257/1050] fix tox tool and fix the some problems --- .../CLI/account/bandwidth_pools_detail.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/account/bandwidth_pools_detail.py b/SoftLayer/CLI/account/bandwidth_pools_detail.py index a555be065..7e609e961 100644 --- a/SoftLayer/CLI/account/bandwidth_pools_detail.py +++ b/SoftLayer/CLI/account/bandwidth_pools_detail.py @@ -22,17 +22,23 @@ def cli(env, identifier): table.align['value'] = 'l' table.add_row(['Id', bandwidths['id']]) table.add_row(['Name', bandwidths['name']]) - table.add_row(['Create Date', bandwidths['createDate']]) + table.add_row(['Create Date', utils.clean_time(bandwidths.get('createDate'), '%Y-%m-%d')]) current = "{} GB".format(utils.lookup(bandwidths, 'billingCyclePublicBandwidthUsage', 'amountOut')) + if current is None: + current = '-' table.add_row(['Current Usage', current]) projected = "{} GB".format(bandwidths.get('projectedPublicBandwidthUsage', 0)) + if projected is None: + projected = '-' table.add_row(['Projected Usage', projected]) inbound = "{} GB".format(bandwidths.get('inboundPublicBandwidthUsage', 0)) + if inbound is None: + inbound = '-' table.add_row(['Inbound Usage', inbound]) if bandwidths['hardware'] != []: table.add_row(['hardware', _bw_table(bandwidths['hardware'])]) else: - table.add_row(['hardware', 'not found']) + table.add_row(['hardware', 'Not Found']) if bandwidths['virtualGuests'] != []: table.add_row(['virtualGuests', _virtual_table(bandwidths['virtualGuests'])]) @@ -40,9 +46,9 @@ def cli(env, identifier): table.add_row(['virtualGuests', 'Not Found']) if bandwidths['bareMetalInstances'] != []: - table.add_row(['Netscale', _bw_table(bandwidths['bareMetalInstances'])]) + table.add_row(['Netscaler', _bw_table(bandwidths['bareMetalInstances'])]) else: - table.add_row(['Netscale', 'Not Found']) + table.add_row(['Netscaler', 'Not Found']) env.fout(table) @@ -51,9 +57,11 @@ def _bw_table(bw_data): """Generates a bandwidth useage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amount')) current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) - ip_address = bw_point['primaryIpAddress'] + ip_address = bw_point.get('primaryIpAddress') + if ip_address is None: + ip_address = '-' table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] @@ -62,8 +70,10 @@ def _virtual_table(bw_data): """Generates a virtual bandwidth usage table""" table_data = formatting.Table(['Id', 'HostName', "IP Address", 'Amount', "Current Usage"]) for bw_point in bw_data: - amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amountOut')) + amount = "{} GB".format(utils.lookup(bw_point, 'bandwidthAllotmentDetail', 'allocation', 'amount')) current = "{} GB".format(bw_point.get('outboundBandwidthUsage', 0)) - ip_address = bw_point['primaryIpAddress'] + ip_address = bw_point.get('primaryIpAddress') + if ip_address is None: + ip_address = '-' table_data.add_row([bw_point['id'], bw_point['fullyQualifiedDomainName'], ip_address, amount, current]) return [table_data] From 377387bceb1cc52a2277b17a824bb70420ec3819 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 28 Feb 2022 17:06:12 -0600 Subject: [PATCH 0258/1050] #1590 basic structure for the DC closure report --- SoftLayer/CLI/report/dc_closures.py | 125 ++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + 2 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/report/dc_closures.py diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py new file mode 100644 index 000000000..1f4e307c4 --- /dev/null +++ b/SoftLayer/CLI/report/dc_closures.py @@ -0,0 +1,125 @@ +"""Metric Utilities""" +import datetime +import itertools +import sys + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +from pprint import pprint as pp + +@click.command(short_help="""Report on Resources in closing datacenters""") +@environment.pass_env +def cli(env): + """Report on Resources in closing datacenters + + Displays a list of Datacenters soon to be shutdown, and any resources on the account +in those locations + """ + + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, +backendRouterName, frontendRouterName]""" + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + # Find all VLANs in the POD that is going to close. + search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" + resource_mask = """mask[ + resource(SoftLayer_Network_Vlan)[ + id,fullyQualifiedName,name,note,vlanNumber,networkSpace, + virtualGuests[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + hardware[id,fullyQualifiedDomainName,billingItem[cancellationDate]], + networkVlanFirewall[id,primaryIpAddress,billingItem[cancellationDate]], + privateNetworkGateways[id,name,networkSpace], + publicNetworkGateways[id,name,networkSpace] + ] + ] + """ + table_title = "Resources in closing datacenters" + resource_table = formatting.Table(["Id", "Name", "Public VLAN", "Private VLAN", "Type", "Datacenter", + "POD", "Cancellation Date"], title=table_title) + resource_table.align = 'l' + for pod in closing_pods: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + vlans = env.client.call('SoftLayer_Search', 'advancedSearch', + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + for vlan in vlans: + resources = process_vlan(vlan.get('resource', {}), resources) + + for resource_type in resources.keys(): + + for resource_object in resources[resource_type].values(): + resource_table.add_row([ + resource_object['id'], + resource_object['name'], + resource_object['vlan'].get('PUBLIC', '-'), + resource_object['vlan'].get('PRIVATE', '-'), + resource_type, + pod.get('datacenterLongName'), + pod.get('backendRouterName'), + resource_object['cancelDate'] + ]) + + env.fout(resource_table) + + +# returns a Table Row for a given resource +def process_vlan(vlan, resources=None): + if resources is None: + resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} + + type_x = "virtual" + for x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'hardware' + for x in vlan.get('hardware', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + + type_x = 'firewall' + for x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + + type_x = 'gateway' + for x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(x.get('id')) + resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + + return resources + +# name_property is what property to use as the name from resource +# vlan is the vlan object +# resource has the data we want +# entry is for any existing data +def build_resource_object(name_property, vlan, resource, entry): + new_entry = { + 'id': resource.get('id'), + 'name': resource.get(name_property), + 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, + 'cancelDate': utils.clean_time(utils.lookup(resource, 'billingItem', 'cancellationDate')) + } + if entry: + entry['vlan'][vlan.get('networkSpace')] = vlan.get('vlanNumber') + else: + entry = new_entry + + return entry \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 02d3420d3..995845419 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -365,6 +365,7 @@ ('report', 'SoftLayer.CLI.report'), ('report:bandwidth', 'SoftLayer.CLI.report.bandwidth:cli'), + ('report:datacenter-closures', 'SoftLayer.CLI.report.dc_closures:cli'), ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), From da2273ee50e983868065270f0a3445f108b59526 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 1 Mar 2022 17:49:45 -0600 Subject: [PATCH 0259/1050] #1590 added docs and unit tests --- SoftLayer/CLI/report/dc_closures.py | 76 ++++++++++----------- docs/cli/reports.rst | 12 +++- tests/CLI/modules/report_tests.py | 102 ++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 39 deletions(-) diff --git a/SoftLayer/CLI/report/dc_closures.py b/SoftLayer/CLI/report/dc_closures.py index 1f4e307c4..563533ab1 100644 --- a/SoftLayer/CLI/report/dc_closures.py +++ b/SoftLayer/CLI/report/dc_closures.py @@ -1,8 +1,4 @@ -"""Metric Utilities""" -import datetime -import itertools -import sys - +"""Report on Resources in closing datacenters""" import click from SoftLayer.CLI import environment @@ -10,14 +6,12 @@ from SoftLayer import utils -from pprint import pprint as pp - @click.command(short_help="""Report on Resources in closing datacenters""") @environment.pass_env def cli(env): """Report on Resources in closing datacenters - Displays a list of Datacenters soon to be shutdown, and any resources on the account + Displays a list of Datacenters soon to be shutdown, and any resources on the account in those locations """ @@ -33,7 +27,7 @@ def cli(env): } mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" - closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask) + closing_pods = env.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) # Find all VLANs in the POD that is going to close. search = "_objectType:SoftLayer_Network_Vlan primaryRouter.hostname: \"{}\" || primaryRouter.hostname: \"{}\"" resource_mask = """mask[ @@ -54,16 +48,17 @@ def cli(env): for pod in closing_pods: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} vlans = env.client.call('SoftLayer_Search', 'advancedSearch', - search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), - iter=True, mask=resource_mask) + search.format(pod.get('backendRouterName'), pod.get('frontendRouterName')), + iter=True, mask=resource_mask) + # Go through the vlans and coalate the resources into a data structure that is easy to print out for vlan in vlans: resources = process_vlan(vlan.get('resource', {}), resources) - - for resource_type in resources.keys(): - - for resource_object in resources[resource_type].values(): + + # Go through each resource and add it to the table + for resource_type, resource_values in resources.items(): + for resource_id, resource_object in resource_values.items(): resource_table.add_row([ - resource_object['id'], + resource_id, resource_object['name'], resource_object['vlan'].get('PUBLIC', '-'), resource_object['vlan'].get('PRIVATE', '-'), @@ -72,46 +67,51 @@ def cli(env): pod.get('backendRouterName'), resource_object['cancelDate'] ]) - + env.fout(resource_table) # returns a Table Row for a given resource def process_vlan(vlan, resources=None): + """Takes in a vlan object and pulls out the needed resources""" if resources is None: resources = {'hardware': {}, 'virtual': {}, 'firewall': {}, 'gateway': {}} type_x = "virtual" - for x in vlan.get('virtualGuests', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('virtualGuests', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'hardware' - for x in vlan.get('hardware', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, x, existing) + for obj_x in vlan.get('hardware', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('fullyQualifiedDomainName', vlan, obj_x, existing) type_x = 'firewall' - for x in vlan.get('networkVlanFirewall', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('primaryIpAddress', vlan, x, existing) + for obj_x in vlan.get('networkVlanFirewall', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('primaryIpAddress', vlan, obj_x, existing) type_x = 'gateway' - for x in vlan.get('privateNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) - for x in vlan.get('publicNetworkGateways', {}): - existing = resources[type_x].get(x.get('id')) - resources[type_x][x['id']] = build_resource_object('name', vlan, x, existing) + for obj_x in vlan.get('privateNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) + for obj_x in vlan.get('publicNetworkGateways', {}): + existing = resources[type_x].get(obj_x.get('id')) + resources[type_x][obj_x['id']] = build_resource_object('name', vlan, obj_x, existing) return resources -# name_property is what property to use as the name from resource -# vlan is the vlan object -# resource has the data we want -# entry is for any existing data + def build_resource_object(name_property, vlan, resource, entry): - new_entry = { + """builds out a resource object and puts the required values in the right place. + + :param: name_property is what property to use as the name from resource + :param: vlan is the vlan object + :param: resource has the data we want + :param: entry is for any existing data + """ + new_entry = { 'id': resource.get('id'), 'name': resource.get(name_property), 'vlan': {vlan.get('networkSpace'): vlan.get('vlanNumber')}, @@ -122,4 +122,4 @@ def build_resource_object(name_property, vlan, resource, entry): else: entry = new_entry - return entry \ No newline at end of file + return entry diff --git a/docs/cli/reports.rst b/docs/cli/reports.rst index f62de5882..39299e99b 100644 --- a/docs/cli/reports.rst +++ b/docs/cli/reports.rst @@ -14,4 +14,14 @@ A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips .. click:: SoftLayer.CLI.report.bandwidth:cli :prog: report bandwidth - :show-nested: \ No newline at end of file + :show-nested: + + +.. click:: SoftLayer.CLI.report.dc_closures:cli + :prog: report datacenter-closures + :show-nested: + +Displays some basic information about the Servers and other resources that are in Datacenters scheduled to be +decommissioned in the near future. +See `IBM Cloud Datacenter Consolidation `_ for +more information \ No newline at end of file diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index f756704c0..f2012ab34 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -7,6 +7,7 @@ from SoftLayer import testing import json +from unittest import mock as mock from pprint import pprint as pp @@ -295,3 +296,104 @@ def test_server_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_dc_closure_report(self): + search_mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + search_mock.side_effect = [_advanced_search(), [], [], []] + result = self.run_command(['report', 'datacenter-closures']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects', filter=mock.ANY, mask=mock.ANY) + self.assert_called_with('SoftLayer_Search', 'advancedSearch') + json_output = json.loads(result.output) + pp(json_output) + self.assertEqual(5, len(json_output)) + self.assertEqual('bcr01a.ams01', json_output[0]['POD']) + + +def _advanced_search(): + results = [{'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.858', + 'hardware': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}, + {'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}], + 'id': 1133383, + 'name': 'Mex-BM-Public', + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 858}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|fcr01a.mex01|'], + 'relevanceScore': '5.4415264', + 'resource': {'fullyQualifiedName': 'mex01.fcr01.1257', + 'hardware': [], + 'id': 2912280, + 'networkSpace': 'PUBLIC', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1257}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '5.003179', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1472', + 'hardware': [], + 'id': 2912282, + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [{'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'imageTest.ibmtest.com', + 'id': 127270182}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'test.deleteme.com', + 'id': 106291032}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testslack.test.com', + 'id': 127889958}], + 'vlanNumber': 1472}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1664', + 'hardware': [{'billingItem': {'cancellationDate': '2022-03-03T23:59:59-06:00'}, + 'fullyQualifiedDomainName': 'testpooling.ibmtest.com', + 'id': 1534033}, + {'billingItem': {'cancellationDate': None}, + 'fullyQualifiedDomainName': 'testpooling2.ibmtest.com', + 'id': 1676221}], + 'id': 3111644, + 'name': 'testmex', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1664}, + 'resourceType': 'SoftLayer_Network_Vlan'}, + {'matchedTerms': ['primaryRouter.hostname:|bcr01a.mex01|'], + 'relevanceScore': '4.9517627', + 'resource': {'fullyQualifiedName': 'mex01.bcr01.1414', + 'hardware': [], + 'id': 2933662, + 'name': 'test-for-trunks', + 'networkSpace': 'PRIVATE', + 'privateNetworkGateways': [], + 'publicNetworkGateways': [], + 'virtualGuests': [], + 'vlanNumber': 1414}, + 'resourceType': 'SoftLayer_Network_Vlan'}] + return results From 4c85a3e6507f8b7aef71ecb30cead241dbf39358 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 14:59:09 -0400 Subject: [PATCH 0260/1050] New Command slcli hardware|virtual monitoring --- SoftLayer/CLI/hardware/monitoring.py | 37 +++++++++++++++++++ SoftLayer/CLI/routes.py | 2 + SoftLayer/CLI/virt/monitoring.py | 37 +++++++++++++++++++ .../fixtures/SoftLayer_Hardware_Server.py | 32 +++++++++++++++- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 31 ++++++++++++++-- SoftLayer/managers/hardware.py | 2 + SoftLayer/managers/vs.py | 1 + tests/CLI/modules/server_tests.py | 4 ++ tests/CLI/modules/vs/vs_tests.py | 4 ++ 9 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 SoftLayer/CLI/hardware/monitoring.py create mode 100644 SoftLayer/CLI/virt/monitoring.py diff --git a/SoftLayer/CLI/hardware/monitoring.py b/SoftLayer/CLI/hardware/monitoring.py new file mode 100644 index 000000000..81640f3e5 --- /dev/null +++ b/SoftLayer/CLI/hardware/monitoring.py @@ -0,0 +1,37 @@ +"""Get monitoring for a hardware device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for a hardware monitors device.""" + + hardware = SoftLayer.HardwareManager(env.client) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + monitoring = hardware.get_hardware(identifier) + + table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['location', monitoring['datacenter']['longName']]) + + monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + for monitor in monitoring['networkMonitors']: + monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), + monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) + + table.add_row(['Devices monitors', monitoring_table]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 3bd02eae9..d8176f1ca 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -52,6 +52,7 @@ ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), + ('virtual:monitoring', 'SoftLayer.CLI.virt.monitoring:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), @@ -280,6 +281,7 @@ ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), + ('hardware:monitoring', 'SoftLayer.CLI.hardware.monitoring:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/CLI/virt/monitoring.py b/SoftLayer/CLI/virt/monitoring.py new file mode 100644 index 000000000..4e76549cf --- /dev/null +++ b/SoftLayer/CLI/virt/monitoring.py @@ -0,0 +1,37 @@ +"""Get monitoring for a vSI device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get details for a vsi monitors device.""" + + vsi = SoftLayer.VSManager(env.client) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + monitoring = vsi.get_instance(identifier) + + table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['location', monitoring['datacenter']['longName']]) + + monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + for monitor in monitoring['networkMonitors']: + monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), + monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) + + table.add_row(['Devices monitors', monitoring_table]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 938c5cebc..0b3a6c748 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -1,7 +1,7 @@ getObject = { 'id': 1000, 'globalIdentifier': '1a2b3c-1701', - 'datacenter': {'id': 50, 'name': 'TEST00', + 'datacenter': {'id': 50, 'name': 'TEST00', 'longName': 'test 00', 'description': 'Test Data Center'}, 'billingItem': { 'id': 6327, @@ -74,7 +74,35 @@ 'friendlyName': 'Friendly Transaction Name', 'id': 6660 } - } + }, + 'networkMonitors': [ + { + 'hardwareId': 3123796, + 'hostId': 3123796, + 'id': 19016454, + 'ipAddress': '169.53.167.199', + 'queryTypeId': 1, + 'responseActionId': 2, + 'status': 'ON', + 'waitCycles': 0, + 'lastResult': { + 'finishTime': '2022-03-10T08:31:40-06:00', + 'responseStatus': 2, + 'responseTime': 159.15, + }, + 'queryType': { + 'description': 'Test ping to address', + 'id': 1, + 'monitorLevel': 0, + 'name': 'SERVICE PING' + }, + 'responseAction': { + 'actionDescription': 'Notify Users', + 'id': 2, + 'level': 0 + } + } + ] } editObject = True setTags = True diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 4662b68f8..f7e422d22 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -47,7 +47,7 @@ 'preset': {'keyName': 'B1_8X16X100'} } }, - 'datacenter': {'id': 50, 'name': 'TEST00', + 'datacenter': {'id': 50, 'name': 'TEST00', 'longName': 'test 00', 'description': 'Test Data Center'}, 'powerState': {'keyName': 'RUNNING', 'name': 'Running'}, 'maxCpu': 2, @@ -83,6 +83,29 @@ 'softwareDescription': {'name': 'Ubuntu'}} }], 'tagReferences': [{'tag': {'name': 'production'}}], + 'networkMonitors': [ + { + 'guestId': 116114480, + 'hostId': 116114480, + 'id': 17653845, + 'ipAddress': '52.116.23.73', + 'queryTypeId': 1, + 'responseActionId': 1, + 'status': 'ON', + 'waitCycles': 0, + 'queryType': { + 'description': 'Test ping to address', + 'id': 1, + 'monitorLevel': 0, + 'name': 'SERVICE PING' + }, + 'responseAction': { + 'actionDescription': 'Do Nothing', + 'id': 1, + 'level': 0 + } + } + ] } getCreateObjectOptions = { 'flavors': [ @@ -894,6 +917,6 @@ allowAccessToNetworkStorageList = True attachDiskImage = { - "createDate": "2021-03-22T13:15:31-06:00", - "id": 1234567 - } + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fe494f83d..4ef7333aa 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -231,6 +231,7 @@ def get_hardware(self, hardware_id, **kwargs): 'domain,' 'provisionDate,' 'hardwareStatus,' + 'bareMetalInstanceFlag,' 'processorPhysicalCoreAmount,' 'memoryCapacity,' 'notes,' @@ -269,6 +270,7 @@ def get_hardware(self, hardware_id, **kwargs): 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' + 'monitoringServiceComponent,networkMonitors[queryType,lastResult,responseAction],' 'remoteManagementAccounts[username,password]' ) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 75c00127a..2fa698dce 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -252,6 +252,7 @@ def get_instance(self, instance_id, **kwargs): 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' 'dedicatedHost.id,' + 'monitoringServiceComponent,networkMonitors[queryType,lastResult,responseAction],' 'placementGroupId' ) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d688a6a90..a150217e2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -1010,3 +1010,7 @@ def test_sensor(self): def test_sensor_discrete(self): result = self.run_command(['hardware', 'sensor', '100', '--discrete']) self.assert_no_fail(result) + + def test_monitoring(self): + result = self.run_command(['hardware', 'monitoring', '100']) + self.assert_no_fail(result) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 982ed0879..4ae31fd6d 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -937,3 +937,7 @@ def test_authorize_volume_and_portable_storage_vs(self): result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', '--portable-id=12345', '1234']) self.assert_no_fail(result) + + def test_monitoring_vs(self): + result = self.run_command(['vs', 'monitoring', '1234']) + self.assert_no_fail(result) From d5e4f1ed197462a8bf28fb9ec6e2625456ee5f76 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 15:13:35 -0400 Subject: [PATCH 0261/1050] add documentation --- docs/cli/hardware.rst | 4 ++++ docs/cli/vs.rst | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 1f8375cfe..6f7ed344e 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -24,6 +24,10 @@ Interacting with Hardware :prog: hardware create :show-nested: +.. click:: SoftLayer.CLI.hardware.monitoring:cli + :prog: hardware monitoring + :show-nested: + Provides some basic functionality to order a server. `slcli order` has a more full featured method of ordering servers. This command only supports the FAST_PROVISION type. diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 6227e0570..3dd34a405 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -271,6 +271,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual authorize-storage :show-nested: +.. click:: SoftLayer.CLI.virt.monitoring:cli + :prog: virtual monitoring + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity From b6c3336fc0120b59088b5ee4f0feba31dbe9a36e Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Mar 2022 18:00:52 -0400 Subject: [PATCH 0262/1050] fix to errors in slcli hw create-options --- SoftLayer/managers/account.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index aadb4af94..15e1ddfef 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -284,6 +284,11 @@ def get_routers(self, location=None, mask=None): :param string location: location string :returns: Routers """ + + if mask is None: + mask = """ + topLevelLocation + """ object_filter = '' if location: object_filter = { From 2abe0e6427ba6840ba3dfbc3c87066b4bcb424c6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 14:22:50 -0600 Subject: [PATCH 0263/1050] v6.0.0 release --- CHANGELOG.md | 16 +++++++++++++++- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9166bc3..799351a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,21 @@ # Change Log -## [5.9.9] - 2021-12-07 +## [6.0.0] - 2022-03-11 + + +## What's Changed +* Replace the use of ptable with prettytable by @dvzrv in https://github.com/softlayer/softlayer-python/pull/1584 +* Bandwidth pool management by @caberos in https://github.com/softlayer/softlayer-python/pull/1582 +* Add id in the result in the command bandwidth-pools by @edsonarios in https://github.com/softlayer/softlayer-python/pull/1586 +* Datacenter closure report by @allmightyspiff in https://github.com/softlayer/softlayer-python/pull/1592 +* fix to errors in slcli hw create-options by @caberos in https://github.com/softlayer/softlayer-python/pull/1594 + + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.0 + + +## [5.9.9] - 2022-02-04 https://github.com/softlayer/softlayer-python/compare/v5.9.8...v5.9.9 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index e7749589d..3c37a4af7 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.9' +VERSION = 'v5.6.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 945dcf95b..cdb4e2500 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.9', + version='5.6.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 09f63db7189272183a58dc4b255f707fbf9a68b4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 14:24:13 -0600 Subject: [PATCH 0264/1050] v6.0.0 version updates --- SoftLayer/consts.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 3c37a4af7..38a8289bf 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.6.0' +VERSION = 'v6.0.0' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index cdb4e2500..211c077d5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.6.0', + version='6.0.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 83915de22ea85b5701cf2c6e25fe48f64f82a196 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:02:09 -0600 Subject: [PATCH 0265/1050] added long_description_content_type to the setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 211c077d5..39bf801f6 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ version='6.0.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, + long_description_content_type='text/x-rst', author='SoftLayer, Inc., an IBM Company', author_email='SLDNDeveloperRelations@wwpdl.vnet.ibm.com', packages=find_packages(exclude=['tests']), From a289994f4c076e2e604b86c19ffdd9875a021a20 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:04:20 -0600 Subject: [PATCH 0266/1050] normalized line endings --- .github/workflows/documentation.yml | 54 +-- .github/workflows/test_pypi_release.yml | 72 ++-- SoftLayer/CLI/account/billing_items.py | 120 +++--- SoftLayer/CLI/account/cancel_item.py | 36 +- SoftLayer/CLI/autoscale/__init__.py | 2 +- SoftLayer/CLI/tags/__init__.py | 2 +- SoftLayer/CLI/tags/cleanup.py | 52 +-- SoftLayer/CLI/tags/details.py | 54 +-- SoftLayer/CLI/tags/list.py | 150 ++++---- SoftLayer/CLI/tags/taggable.py | 54 +-- SoftLayer/CLI/virt/migrate.py | 164 ++++---- SoftLayer/fixtures/BluePages_Search.py | 2 +- SoftLayer/fixtures/SoftLayer_Hardware.py | 178 ++++----- SoftLayer/fixtures/SoftLayer_Search.py | 46 +-- SoftLayer/fixtures/SoftLayer_Tag.py | 62 +-- SoftLayer/managers/tags.py | 460 +++++++++++------------ docCheck.py | 188 ++++----- docs/cli/nas.rst | 22 +- docs/cli/tags.rst | 60 +-- tests/CLI/modules/tag_tests.py | 226 +++++------ tests/managers/tag_tests.py | 416 ++++++++++---------- 21 files changed, 1210 insertions(+), 1210 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c713212ee..d307fd937 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,27 +1,27 @@ -name: documentation - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Documentation Checks - run: | - python docCheck.py +name: documentation + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Documentation Checks + run: | + python docCheck.py diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index aea906c54..12443d257 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -1,37 +1,37 @@ -# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ - -name: Publish 📦 to TestPyPI - -on: - push: - branches: [test-pypi ] - -jobs: - build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Set up Python 3.8 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install pypa/build - run: >- - python -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: >- - python -m - build - --sdist - --wheel - --outdir dist/ - . - - name: Publish 📦 to Test PyPI - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{ secrets.CGALLO_TEST_PYPI }} +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Publish 📦 to TestPyPI + +on: + push: + branches: [test-pypi ] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/SoftLayer/CLI/account/billing_items.py b/SoftLayer/CLI/account/billing_items.py index 32bc6c271..48abe4644 100644 --- a/SoftLayer/CLI/account/billing_items.py +++ b/SoftLayer/CLI/account/billing_items.py @@ -1,60 +1,60 @@ -"""Lists all active billing items on this account. See https://cloud.ibm.com/billing/billing-items""" -# :license: MIT, see LICENSE for more details. -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager as AccountManager -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - """Lists billing items with some other useful information. - - Similiar to https://cloud.ibm.com/billing/billing-items - """ - - manager = AccountManager(env.client) - items = manager.get_account_billing_items() - table = item_table(items) - - env.fout(table) - - -def item_table(items): - """Formats a table for billing items""" - table = formatting.Table([ - "Id", - "Create Date", - "Cost", - "Category Code", - "Ordered By", - "Description", - "Notes" - ], title="Billing Items") - table.align['Description'] = 'l' - table.align['Category Code'] = 'l' - for item in items: - description = item.get('description') - fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) - if fqdn != ".": - description = fqdn - user = utils.lookup(item, 'orderItem', 'order', 'userRecord') - ordered_by = "IBM" - create_date = utils.clean_time(item.get('createDate'), in_format='%Y-%m-%d', out_format='%Y-%m-%d') - if user: - # ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) - ordered_by = user.get('displayName') - - table.add_row([ - item.get('id'), - create_date, - item.get('nextInvoiceTotalRecurringAmount'), - item.get('categoryCode'), - ordered_by, - utils.trim_to(description, 50), - utils.trim_to(item.get('notes', 'None'), 40), - ]) - return table +"""Lists all active billing items on this account. See https://cloud.ibm.com/billing/billing-items""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Lists billing items with some other useful information. + + Similiar to https://cloud.ibm.com/billing/billing-items + """ + + manager = AccountManager(env.client) + items = manager.get_account_billing_items() + table = item_table(items) + + env.fout(table) + + +def item_table(items): + """Formats a table for billing items""" + table = formatting.Table([ + "Id", + "Create Date", + "Cost", + "Category Code", + "Ordered By", + "Description", + "Notes" + ], title="Billing Items") + table.align['Description'] = 'l' + table.align['Category Code'] = 'l' + for item in items: + description = item.get('description') + fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) + if fqdn != ".": + description = fqdn + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + ordered_by = "IBM" + create_date = utils.clean_time(item.get('createDate'), in_format='%Y-%m-%d', out_format='%Y-%m-%d') + if user: + # ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + ordered_by = user.get('displayName') + + table.add_row([ + item.get('id'), + create_date, + item.get('nextInvoiceTotalRecurringAmount'), + item.get('categoryCode'), + ordered_by, + utils.trim_to(description, 50), + utils.trim_to(item.get('notes', 'None'), 40), + ]) + return table diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py index de0fa446b..0cc08593d 100644 --- a/SoftLayer/CLI/account/cancel_item.py +++ b/SoftLayer/CLI/account/cancel_item.py @@ -1,18 +1,18 @@ -"""Cancels a billing item.""" -# :license: MIT, see LICENSE for more details. -import click - -from SoftLayer.CLI import environment -from SoftLayer.managers.account import AccountManager as AccountManager - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Cancels a billing item.""" - - manager = AccountManager(env.client) - item = manager.cancel_item(identifier) - - env.fout(item) +"""Cancels a billing item.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.account import AccountManager as AccountManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancels a billing item.""" + + manager = AccountManager(env.client) + item = manager.cancel_item(identifier) + + env.fout(item) diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py index 80cd82747..81d126383 100644 --- a/SoftLayer/CLI/autoscale/__init__.py +++ b/SoftLayer/CLI/autoscale/__init__.py @@ -1 +1 @@ -"""Autoscale""" +"""Autoscale""" diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py index f8dd3783b..5b257eeec 100644 --- a/SoftLayer/CLI/tags/__init__.py +++ b/SoftLayer/CLI/tags/__init__.py @@ -1 +1 @@ -"""Manage Tags""" +"""Manage Tags""" diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py index 26ddea7ef..54da9b20d 100644 --- a/SoftLayer/CLI/tags/cleanup.py +++ b/SoftLayer/CLI/tags/cleanup.py @@ -1,26 +1,26 @@ -"""Removes unused Tags""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.managers.tags import TagManager - - -@click.command() -@click.option('--dry-run', '-d', is_flag=True, default=False, - help="Don't delete, just show what will be deleted.") -@environment.pass_env -def cli(env, dry_run): - """Removes all empty tags.""" - - tag_manager = TagManager(env.client) - empty_tags = tag_manager.get_unattached_tags() - - for tag in empty_tags: - if dry_run: - click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') - else: - result = tag_manager.delete_tag(tag.get('name')) - color = 'green' if result else 'red' - click.secho("Removing {}".format(tag.get('name')), fg=color) +"""Removes unused Tags""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.option('--dry-run', '-d', is_flag=True, default=False, + help="Don't delete, just show what will be deleted.") +@environment.pass_env +def cli(env, dry_run): + """Removes all empty tags.""" + + tag_manager = TagManager(env.client) + empty_tags = tag_manager.get_unattached_tags() + + for tag in empty_tags: + if dry_run: + click.secho("(Dry Run) Removing {}".format(tag.get('name')), fg='yellow') + else: + result = tag_manager.delete_tag(tag.get('name')) + color = 'green' if result else 'red' + click.secho("Removing {}".format(tag.get('name')), fg=color) diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py index 7c397f431..6e75013d5 100644 --- a/SoftLayer/CLI/tags/details.py +++ b/SoftLayer/CLI/tags/details.py @@ -1,27 +1,27 @@ -"""Details of a Tag.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI.tags.list import detailed_table -from SoftLayer.managers.tags import TagManager - - -@click.command() -@click.argument('identifier') -@click.option('--name', required=False, default=False, is_flag=True, show_default=False, - help='Assume identifier is a tag name. Useful if your tag name is a number.') -@environment.pass_env -def cli(env, identifier, name): - """Get details for a Tag. Identifier can be either a name or tag-id""" - - tag_manager = TagManager(env.client) - - # If the identifier is a int, and user didn't tell us it was a name. - if str.isdigit(identifier) and not name: - tags = [tag_manager.get_tag(identifier)] - else: - tags = tag_manager.get_tag_by_name(identifier) - table = detailed_table(tag_manager, tags) - env.fout(table) +"""Details of a Tag.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI.tags.list import detailed_table +from SoftLayer.managers.tags import TagManager + + +@click.command() +@click.argument('identifier') +@click.option('--name', required=False, default=False, is_flag=True, show_default=False, + help='Assume identifier is a tag name. Useful if your tag name is a number.') +@environment.pass_env +def cli(env, identifier, name): + """Get details for a Tag. Identifier can be either a name or tag-id""" + + tag_manager = TagManager(env.client) + + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: + tags = [tag_manager.get_tag(identifier)] + else: + tags = tag_manager.get_tag_by_name(identifier) + table = detailed_table(tag_manager, tags) + env.fout(table) diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index bc8662764..f811d573c 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -1,75 +1,75 @@ -"""List Tags.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers.tags import TagManager -from SoftLayer import utils - -# pylint: disable=unnecessary-lambda - - -@click.command() -@click.option('--detail', '-d', is_flag=True, default=False, - help="Show information about the resources using this tag.") -@environment.pass_env -def cli(env, detail): - """List Tags.""" - - tag_manager = TagManager(env.client) - - if detail: - tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) - for table in tables: - env.fout(table) - else: - table = simple_table(tag_manager) - env.fout(table) - # pp(tags.list_tags()) - - -def tag_row(tag): - """Format a tag table row""" - return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] - - -def detailed_table(tag_manager, tags): - """Creates a table for each tag, with details about resources using it""" - tables = [] - for tag in tags: - references = tag_manager.get_tag_references(tag.get('id')) - # pp(references) - new_table = formatting.Table(['Id', 'Type', 'Resource'], title=tag.get('name')) - for reference in references: - tag_type = utils.lookup(reference, 'tagType', 'keyName') - resource_id = reference.get('resourceTableId') - resource_row = get_resource_name(tag_manager, resource_id, tag_type) - new_table.add_row([resource_id, tag_type, resource_row]) - tables.append(new_table) - - return tables - - -def simple_table(tag_manager): - """Just tags and how many resources on each""" - tags = tag_manager.list_tags() - table = formatting.Table(['Id', 'Tag', 'Count'], title='Tags') - for tag in tags.get('attached', []): - table.add_row(tag_row(tag)) - for tag in tags.get('unattached', []): - table.add_row(tag_row(tag)) - return table - - -def get_resource_name(tag_manager, resource_id, tag_type): - """Returns a string to identify a resource""" - name = None - try: - resource = tag_manager.reference_lookup(resource_id, tag_type) - name = tag_manager.get_resource_name(resource, tag_type) - except SoftLayerAPIError as exception: - name = "{}".format(exception.reason) - return name +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + + +@click.command() +@click.option('--detail', '-d', is_flag=True, default=False, + help="Show information about the resources using this tag.") +@environment.pass_env +def cli(env, detail): + """List Tags.""" + + tag_manager = TagManager(env.client) + + if detail: + tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) + for table in tables: + env.fout(table) + else: + table = simple_table(tag_manager) + env.fout(table) + # pp(tags.list_tags()) + + +def tag_row(tag): + """Format a tag table row""" + return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] + + +def detailed_table(tag_manager, tags): + """Creates a table for each tag, with details about resources using it""" + tables = [] + for tag in tags: + references = tag_manager.get_tag_references(tag.get('id')) + # pp(references) + new_table = formatting.Table(['Id', 'Type', 'Resource'], title=tag.get('name')) + for reference in references: + tag_type = utils.lookup(reference, 'tagType', 'keyName') + resource_id = reference.get('resourceTableId') + resource_row = get_resource_name(tag_manager, resource_id, tag_type) + new_table.add_row([resource_id, tag_type, resource_row]) + tables.append(new_table) + + return tables + + +def simple_table(tag_manager): + """Just tags and how many resources on each""" + tags = tag_manager.list_tags() + table = formatting.Table(['Id', 'Tag', 'Count'], title='Tags') + for tag in tags.get('attached', []): + table.add_row(tag_row(tag)) + for tag in tags.get('unattached', []): + table.add_row(tag_row(tag)) + return table + + +def get_resource_name(tag_manager, resource_id, tag_type): + """Returns a string to identify a resource""" + name = None + try: + resource = tag_manager.reference_lookup(resource_id, tag_type) + name = tag_manager.get_resource_name(resource, tag_type) + except SoftLayerAPIError as exception: + name = "{}".format(exception.reason) + return name diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 0c08acdb0..4bbd4a926 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -1,27 +1,27 @@ -"""List everything that could be tagged.""" -# :license: MIT, see LICENSE for more details. - -import click - -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer.managers.tags import TagManager - - -@click.command() -@environment.pass_env -def cli(env): - """List everything that could be tagged.""" - - tag_manager = TagManager(env.client) - tag_types = tag_manager.get_all_tag_types() - for tag_type in tag_types: - title = "{} ({})".format(tag_type['description'], tag_type['keyName']) - table = formatting.Table(['Id', 'Name'], title=title) - resources = tag_manager.taggable_by_type(tag_type['keyName']) - for resource in resources: - table.add_row([ - resource['resource']['id'], - tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) - ]) - env.fout(table) +"""List everything that could be tagged.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.tags import TagManager + + +@click.command() +@environment.pass_env +def cli(env): + """List everything that could be tagged.""" + + tag_manager = TagManager(env.client) + tag_types = tag_manager.get_all_tag_types() + for tag_type in tag_types: + title = "{} ({})".format(tag_type['description'], tag_type['keyName']) + table = formatting.Table(['Id', 'Name'], title=title) + resources = tag_manager.taggable_by_type(tag_type['keyName']) + for resource in resources: + table.add_row([ + resource['resource']['id'], + tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) + ]) + env.fout(table) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index d06673365..df44245f7 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -1,82 +1,82 @@ -"""Manage Migrations of Virtual Guests""" -# :license: MIT, see LICENSE for more details. -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - - -@click.command() -@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.") -@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False, - help="Migrate ALL guests that require migration immediately.") -@click.option('--host', '-h', type=click.INT, - help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.") -@environment.pass_env -def cli(env, guest, migrate_all, host): - """Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well.""" - - vsi = SoftLayer.VSManager(env.client) - pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} - dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} - mask = """mask[ - id, hostname, domain, datacenter, pendingMigrationFlag, powerState, - primaryIpAddress,primaryBackendIpAddress, dedicatedHost - ]""" - - # No options, just print out a list of guests that can be migrated - if not (guest or migrate_all): - require_migration = vsi.list_instances(filter=pending_filter, mask=mask) - require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration") - - for vsi_object in require_migration: - require_table.add_row([ - vsi_object.get('id'), - vsi_object.get('hostname'), - vsi_object.get('domain'), - utils.lookup(vsi_object, 'datacenter', 'name') - ]) - - if require_migration: - env.fout(require_table) - else: - click.secho("No guests require migration at this time", fg='green') - - migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask) - migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], - title="Dedicated Guests") - for vsi_object in migrateable: - migrateable_table.add_row([ - vsi_object.get('id'), - vsi_object.get('hostname'), - vsi_object.get('domain'), - utils.lookup(vsi_object, 'datacenter', 'name'), - utils.lookup(vsi_object, 'dedicatedHost', 'name'), - utils.lookup(vsi_object, 'dedicatedHost', 'id') - ]) - env.fout(migrateable_table) - # Migrate all guests with pendingMigrationFlag=True - elif migrate_all: - require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") - if not require_migration: - click.secho("No guests require migration at this time", fg='green') - for vsi_object in require_migration: - migrate(vsi, vsi_object['id']) - # Just migrate based on the options - else: - migrate(vsi, guest, host) - - -def migrate(vsi_manager, vsi_id, host_id=None): - """Handles actually migrating virtual guests and handling the exception""" - - try: - if host_id: - vsi_manager.migrate_dedicated(vsi_id, host_id) - else: - vsi_manager.migrate(vsi_id) - click.secho("Started a migration on {}".format(vsi_id), fg='green') - except SoftLayer.exceptions.SoftLayerAPIError as ex: - click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red') +"""Manage Migrations of Virtual Guests""" +# :license: MIT, see LICENSE for more details. +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.") +@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False, + help="Migrate ALL guests that require migration immediately.") +@click.option('--host', '-h', type=click.INT, + help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.") +@environment.pass_env +def cli(env, guest, migrate_all, host): + """Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well.""" + + vsi = SoftLayer.VSManager(env.client) + pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} + dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} + mask = """mask[ + id, hostname, domain, datacenter, pendingMigrationFlag, powerState, + primaryIpAddress,primaryBackendIpAddress, dedicatedHost + ]""" + + # No options, just print out a list of guests that can be migrated + if not (guest or migrate_all): + require_migration = vsi.list_instances(filter=pending_filter, mask=mask) + require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration") + + for vsi_object in require_migration: + require_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name') + ]) + + if require_migration: + env.fout(require_table) + else: + click.secho("No guests require migration at this time", fg='green') + + migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask) + migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'], + title="Dedicated Guests") + for vsi_object in migrateable: + migrateable_table.add_row([ + vsi_object.get('id'), + vsi_object.get('hostname'), + vsi_object.get('domain'), + utils.lookup(vsi_object, 'datacenter', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'name'), + utils.lookup(vsi_object, 'dedicatedHost', 'id') + ]) + env.fout(migrateable_table) + # Migrate all guests with pendingMigrationFlag=True + elif migrate_all: + require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") + if not require_migration: + click.secho("No guests require migration at this time", fg='green') + for vsi_object in require_migration: + migrate(vsi, vsi_object['id']) + # Just migrate based on the options + else: + migrate(vsi, guest, host) + + +def migrate(vsi_manager, vsi_id, host_id=None): + """Handles actually migrating virtual guests and handling the exception""" + + try: + if host_id: + vsi_manager.migrate_dedicated(vsi_id, host_id) + else: + vsi_manager.migrate(vsi_id) + click.secho("Started a migration on {}".format(vsi_id), fg='green') + except SoftLayer.exceptions.SoftLayerAPIError as ex: + click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red') diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py index 9682f63dc..756b02afd 100644 --- a/SoftLayer/fixtures/BluePages_Search.py +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -1 +1 @@ -findBluePagesProfile = True +findBluePagesProfile = True diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 770de045c..3c74dc439 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -1,89 +1,89 @@ -getObject = { - 'id': 1234, - 'globalIdentifier': 'xxxxc-asd', - 'datacenter': {'id': 12, 'name': 'DALLAS21', - 'description': 'Dallas 21'}, - 'billingItem': { - 'id': 6327, - 'recurringFee': 1.54, - 'nextInvoiceTotalRecurringAmount': 16.08, - 'children': [ - {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, - ], - 'orderItem': { - 'order': { - 'userRecord': { - 'username': 'bob', - } - } - } - }, - 'primaryIpAddress': '4.4.4.4', - 'hostname': 'testtest1', - 'domain': 'test.sftlyr.ws', - 'bareMetalInstanceFlag': True, - 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', - 'processorPhysicalCoreAmount': 4, - 'memoryCapacity': 4, - 'primaryBackendIpAddress': '10.4.4.4', - 'networkManagementIpAddress': '10.4.4.4', - 'hardwareStatus': {'status': 'ACTIVE'}, - 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, - 'provisionDate': '2020-08-01 15:23:45', - 'notes': 'NOTES NOTES NOTES', - 'operatingSystem': { - 'softwareLicense': { - 'softwareDescription': { - 'referenceCode': 'UBUNTU_20_64', - 'name': 'Ubuntu', - 'version': 'Ubuntu 20.04 LTS', - } - }, - 'passwords': [ - {'username': 'root', 'password': 'xxxxxxxxxxxx'} - ], - }, - 'remoteManagementAccounts': [ - {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} - ], - 'networkVlans': [ - { - 'networkSpace': 'PRIVATE', - 'vlanNumber': 1234, - 'id': 11111 - }, - ], - 'tagReferences': [ - {'tag': {'name': 'a tag'}} - ], -} - -allowAccessToNetworkStorageList = True - -getSensorData = [ - { - "sensorId": "Ambient 1 Temperature", - "sensorReading": "25.000", - "sensorUnits": "degrees C", - "status": "ok", - "upperCritical": "43.000", - "upperNonCritical": "41.000", - "upperNonRecoverable": "46.000" - }, - { - "lowerCritical": "3500.000", - "sensorId": "Fan 1 Tach", - "sensorReading": "6580.000", - "sensorUnits": "RPM", - "status": "ok" - }, { - "sensorId": "IPMI Watchdog", - "sensorReading": "0x0", - "sensorUnits": "discrete", - "status": "0x0080" - }, { - "sensorId": "Avg Power", - "sensorReading": "70.000", - "sensorUnits": "Watts", - "status": "ok" - }] +getObject = { + 'id': 1234, + 'globalIdentifier': 'xxxxc-asd', + 'datacenter': {'id': 12, 'name': 'DALLAS21', + 'description': 'Dallas 21'}, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'nextInvoiceTotalRecurringAmount': 16.08, + 'children': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, + ], + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'bob', + } + } + } + }, + 'primaryIpAddress': '4.4.4.4', + 'hostname': 'testtest1', + 'domain': 'test.sftlyr.ws', + 'bareMetalInstanceFlag': True, + 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 4, + 'memoryCapacity': 4, + 'primaryBackendIpAddress': '10.4.4.4', + 'networkManagementIpAddress': '10.4.4.4', + 'hardwareStatus': {'status': 'ACTIVE'}, + 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, + 'provisionDate': '2020-08-01 15:23:45', + 'notes': 'NOTES NOTES NOTES', + 'operatingSystem': { + 'softwareLicense': { + 'softwareDescription': { + 'referenceCode': 'UBUNTU_20_64', + 'name': 'Ubuntu', + 'version': 'Ubuntu 20.04 LTS', + } + }, + 'passwords': [ + {'username': 'root', 'password': 'xxxxxxxxxxxx'} + ], + }, + 'remoteManagementAccounts': [ + {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} + ], + 'networkVlans': [ + { + 'networkSpace': 'PRIVATE', + 'vlanNumber': 1234, + 'id': 11111 + }, + ], + 'tagReferences': [ + {'tag': {'name': 'a tag'}} + ], +} + +allowAccessToNetworkStorageList = True + +getSensorData = [ + { + "sensorId": "Ambient 1 Temperature", + "sensorReading": "25.000", + "sensorUnits": "degrees C", + "status": "ok", + "upperCritical": "43.000", + "upperNonCritical": "41.000", + "upperNonRecoverable": "46.000" + }, + { + "lowerCritical": "3500.000", + "sensorId": "Fan 1 Tach", + "sensorReading": "6580.000", + "sensorUnits": "RPM", + "status": "ok" + }, { + "sensorId": "IPMI Watchdog", + "sensorReading": "0x0", + "sensorUnits": "discrete", + "status": "0x0080" + }, { + "sensorId": "Avg Power", + "sensorReading": "70.000", + "sensorUnits": "Watts", + "status": "ok" + }] diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index ccb45fe55..2bac09e42 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -1,23 +1,23 @@ -advancedSearch = [ - { - "relevanceScore": "4", - "resourceType": "SoftLayer_Hardware", - "resource": { - "accountId": 307608, - "domain": "vmware.test.com", - "fullyQualifiedDomainName": "host14.vmware.test.com", - "hardwareStatusId": 5, - "hostname": "host14", - "id": 123456, - "manufacturerSerialNumber": "AAAAAAAAA", - "notes": "A test notes", - "provisionDate": "2018-08-24T12:32:10-06:00", - "serialNumber": "SL12345678", - "serviceProviderId": 1, - "hardwareStatus": { - "id": 5, - "status": "ACTIVE" - } - } - } -] +advancedSearch = [ + { + "relevanceScore": "4", + "resourceType": "SoftLayer_Hardware", + "resource": { + "accountId": 307608, + "domain": "vmware.test.com", + "fullyQualifiedDomainName": "host14.vmware.test.com", + "hardwareStatusId": 5, + "hostname": "host14", + "id": 123456, + "manufacturerSerialNumber": "AAAAAAAAA", + "notes": "A test notes", + "provisionDate": "2018-08-24T12:32:10-06:00", + "serialNumber": "SL12345678", + "serviceProviderId": 1, + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + } + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 9f6aeaec4..79310b354 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -1,31 +1,31 @@ -getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] -getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] -getReferences = [ - { - 'id': 73009305, - 'resourceTableId': 33488921, - 'tag': { - 'id': 1286571, - 'name': 'bs_test_instance', - }, - 'tagId': 1286571, - 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, - 'tagTypeId': 2, - 'usrRecordId': 6625205 - } -] - -deleteTag = True - -setTags = True - -getObject = getAttachedTagsForCurrentUser[0] - -getTagByTagName = getAttachedTagsForCurrentUser - -getAllTagTypes = [ - { - "description": "Hardware", - "keyName": "HARDWARE" - } -] +getUnattachedTagsForCurrentUser = [{'id': 287895, 'name': 'coreos', 'referenceCount': 0}] +getAttachedTagsForCurrentUser = [{'id': 1286571, 'name': 'bs_test_instance', 'referenceCount': 5}] +getReferences = [ + { + 'id': 73009305, + 'resourceTableId': 33488921, + 'tag': { + 'id': 1286571, + 'name': 'bs_test_instance', + }, + 'tagId': 1286571, + 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, + 'tagTypeId': 2, + 'usrRecordId': 6625205 + } +] + +deleteTag = True + +setTags = True + +getObject = getAttachedTagsForCurrentUser[0] + +getTagByTagName = getAttachedTagsForCurrentUser + +getAllTagTypes = [ + { + "description": "Hardware", + "keyName": "HARDWARE" + } +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 818b0547d..eda95790a 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -1,230 +1,230 @@ -""" - SoftLayer.tags - ~~~~~~~~~~~~ - Tag Manager - - :license: MIT, see LICENSE for more details. -""" -import re - -from SoftLayer.exceptions import SoftLayerAPIError - - -class TagManager(object): - """Manager for Tag functions.""" - - def __init__(self, client): - self.client = client - - def list_tags(self, mask=None): - """Returns a list of all tags for the Current User - - :param str mask: Object mask to use if you do not want the default. - """ - if mask is None: - mask = "mask[id,name,referenceCount]" - unattached = self.get_unattached_tags(mask) - attached = self.get_attached_tags(mask) - return {'attached': attached, 'unattached': unattached} - - def get_unattached_tags(self, mask=None): - """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser() - - :params string mask: Mask to use. - """ - return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', - mask=mask, iter=True) - - def get_attached_tags(self, mask=None): - """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser() - - :params string mask: Mask to use. - """ - return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', - mask=mask, iter=True) - - def get_tag_references(self, tag_id, mask=None): - """Calls SoftLayer_Tag::getReferences(id=tag_id) - - :params int tag_id: Tag id to get references from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[tagType]" - return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) - - def get_tag(self, tag_id, mask=None): - """Calls SoftLayer_Tag::getObject(id=tag_id) - - :params int tag_id: Tag id to get object from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[id,name]" - result = self.client.call('SoftLayer_Tag', 'getObject', id=tag_id, mask=mask) - return result - - def get_tag_by_name(self, tag_name, mask=None): - """Calls SoftLayer_Tag::getTagByTagName(tag_name) - - :params string tag_name: Tag name to get object from - :params string mask: Mask to use. - """ - if mask is None: - mask = "mask[id,name]" - result = self.client.call('SoftLayer_Tag', 'getTagByTagName', tag_name, mask=mask) - return result - - def reference_lookup(self, resource_table_id, tag_type): - """Returns the SoftLayer Service for the corresponding type - - :param int resource_table_id: Tag_Reference::resourceTableId - :param string tag_type: Tag_Reference->tagType->keyName - - From SoftLayer_Tag::getAllTagTypes() - - |Type |Service | - | ----------------------------- | ------ | - |Hardware |HARDWARE| - |CCI |GUEST| - |Account Document |ACCOUNT_DOCUMENT| - |Ticket |TICKET| - |Vlan Firewall |NETWORK_VLAN_FIREWALL| - |Contract |CONTRACT| - |Image Template |IMAGE_TEMPLATE| - |Application Delivery Controller |APPLICATION_DELIVERY_CONTROLLER| - |Vlan |NETWORK_VLAN| - |Dedicated Host |DEDICATED_HOST| - """ - service = self.type_to_service(tag_type) - if service is None: - raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - return self.client.call(service, 'getObject', id=resource_table_id) - - def delete_tag(self, name): - """Calls SoftLayer_Tag::deleteTag - - :param string name: tag name to delete - """ - return self.client.call('SoftLayer_Tag', 'deleteTag', name) - - def set_tags(self, tags, key_name, resource_id): - """Calls SoftLayer_Tag::setTags() - - :param string tags: List of tags. - :param string key_name: Key name of a tag type. - :param int resource_id: ID of the object being tagged. - """ - return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) - - def get_all_tag_types(self): - """Calls SoftLayer_Tag::getAllTagTypes()""" - types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') - useable_types = [] - for tag_type in types: - service = self.type_to_service(tag_type['keyName']) - # Mostly just to remove the types that are not user taggable. - if service is not None: - temp_type = tag_type - temp_type['service'] = service - useable_types.append(temp_type) - return useable_types - - def taggable_by_type(self, tag_type): - """Returns a list of resources that can be tagged, that are of the given type - - :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes - """ - service = self.type_to_service(tag_type) - search_term = "_objectType:SoftLayer_{}".format(service) - if tag_type == 'TICKET': - search_term = "{} status.name: open".format(search_term) - elif tag_type == 'IMAGE_TEMPLATE': - mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" - resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', - mask=mask, iter=True) - to_return = [] - # Fake search result output - for resource in resources: - to_return.append({'resourceType': service, 'resource': resource}) - return to_return - elif tag_type == 'NETWORK_SUBNET': - resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) - to_return = [] - # Fake search result output - for resource in resources: - to_return.append({'resourceType': service, 'resource': resource}) - return to_return - resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) - return resources - - @staticmethod - def type_to_service(tag_type): - """Returns the SoftLayer service for the given tag_type""" - service = None - if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - return None - - if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - service = 'Network_Application_Delivery_Controller' - elif tag_type == 'GUEST': - service = 'Virtual_Guest' - elif tag_type == 'DEDICATED_HOST': - service = 'Virtual_DedicatedHost' - elif tag_type == 'IMAGE_TEMPLATE': - service = 'Virtual_Guest_Block_Device_Template_Group' - else: - - tag_type = tag_type.lower() - # Sets the First letter, and any letter preceeded by a '_' to uppercase - # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. - service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) - return service - - @staticmethod - def get_resource_name(resource, tag_type): - """Returns a string that names a resource - - :param dict resource: A SoftLayer datatype for the given tag_type - :param string tag_type: Key name for the tag_type - """ - if tag_type == 'NETWORK_VLAN_FIREWALL': - return resource.get('primaryIpAddress') - elif tag_type == 'NETWORK_VLAN': - return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) - elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - return resource.get('name') - elif tag_type == 'TICKET': - return resource.get('subjet') - elif tag_type == 'NETWORK_SUBNET': - return resource.get('networkIdentifier') - else: - return resource.get('fullyQualifiedDomainName') - - # @staticmethod - # def type_to_datatype(tag_type): - # """Returns the SoftLayer datatye for the given tag_type""" - # datatye = None - # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: - # return None - - # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': - # datatye = 'adcLoadBalancers' - # elif tag_type == 'GUEST': - # datatye = 'virtualGuests' - # elif tag_type == 'DEDICATED_HOST': - # datatye = 'dedicatedHosts' - # elif tag_type == 'HARDWARE': - # datatye = 'hardware' - # elif tag_type == 'TICKET': - # datatye = 'openTickets' - # elif tag_type == 'NETWORK_SUBNET': - # datatye = 'subnets' - # elif tag_type == 'NETWORK_VLAN': - # datatye = 'networkVlans' - # elif tag_type == 'NETWORK_VLAN_FIREWALL': - # datatye = 'networkVlans' - # elif tag_type == 'IMAGE_TEMPLATE': - # datatye = 'blockDeviceTemplateGroups' - - # return datatye +""" + SoftLayer.tags + ~~~~~~~~~~~~ + Tag Manager + + :license: MIT, see LICENSE for more details. +""" +import re + +from SoftLayer.exceptions import SoftLayerAPIError + + +class TagManager(object): + """Manager for Tag functions.""" + + def __init__(self, client): + self.client = client + + def list_tags(self, mask=None): + """Returns a list of all tags for the Current User + + :param str mask: Object mask to use if you do not want the default. + """ + if mask is None: + mask = "mask[id,name,referenceCount]" + unattached = self.get_unattached_tags(mask) + attached = self.get_attached_tags(mask) + return {'attached': attached, 'unattached': unattached} + + def get_unattached_tags(self, mask=None): + """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ + return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_attached_tags(self, mask=None): + """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser() + + :params string mask: Mask to use. + """ + return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_tag_references(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getReferences(id=tag_id) + + :params int tag_id: Tag id to get references from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[tagType]" + return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) + + def get_tag(self, tag_id, mask=None): + """Calls SoftLayer_Tag::getObject(id=tag_id) + + :params int tag_id: Tag id to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getObject', id=tag_id, mask=mask) + return result + + def get_tag_by_name(self, tag_name, mask=None): + """Calls SoftLayer_Tag::getTagByTagName(tag_name) + + :params string tag_name: Tag name to get object from + :params string mask: Mask to use. + """ + if mask is None: + mask = "mask[id,name]" + result = self.client.call('SoftLayer_Tag', 'getTagByTagName', tag_name, mask=mask) + return result + + def reference_lookup(self, resource_table_id, tag_type): + """Returns the SoftLayer Service for the corresponding type + + :param int resource_table_id: Tag_Reference::resourceTableId + :param string tag_type: Tag_Reference->tagType->keyName + + From SoftLayer_Tag::getAllTagTypes() + + |Type |Service | + | ----------------------------- | ------ | + |Hardware |HARDWARE| + |CCI |GUEST| + |Account Document |ACCOUNT_DOCUMENT| + |Ticket |TICKET| + |Vlan Firewall |NETWORK_VLAN_FIREWALL| + |Contract |CONTRACT| + |Image Template |IMAGE_TEMPLATE| + |Application Delivery Controller |APPLICATION_DELIVERY_CONTROLLER| + |Vlan |NETWORK_VLAN| + |Dedicated Host |DEDICATED_HOST| + """ + service = self.type_to_service(tag_type) + if service is None: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + return self.client.call(service, 'getObject', id=resource_table_id) + + def delete_tag(self, name): + """Calls SoftLayer_Tag::deleteTag + + :param string name: tag name to delete + """ + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + + def set_tags(self, tags, key_name, resource_id): + """Calls SoftLayer_Tag::setTags() + + :param string tags: List of tags. + :param string key_name: Key name of a tag type. + :param int resource_id: ID of the object being tagged. + """ + return self.client.call('SoftLayer_Tag', 'setTags', tags, key_name, resource_id) + + def get_all_tag_types(self): + """Calls SoftLayer_Tag::getAllTagTypes()""" + types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') + useable_types = [] + for tag_type in types: + service = self.type_to_service(tag_type['keyName']) + # Mostly just to remove the types that are not user taggable. + if service is not None: + temp_type = tag_type + temp_type['service'] = service + useable_types.append(temp_type) + return useable_types + + def taggable_by_type(self, tag_type): + """Returns a list of resources that can be tagged, that are of the given type + + :param string tag_type: Key name of a tag type. See SoftLayer_Tag::getAllTagTypes + """ + service = self.type_to_service(tag_type) + search_term = "_objectType:SoftLayer_{}".format(service) + if tag_type == 'TICKET': + search_term = "{} status.name: open".format(search_term) + elif tag_type == 'IMAGE_TEMPLATE': + mask = "mask[id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,imageType]" + resources = self.client.call('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups', + mask=mask, iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType': service, 'resource': resource}) + return to_return + elif tag_type == 'NETWORK_SUBNET': + resources = self.client.call('SoftLayer_Account', 'getSubnets', iter=True) + to_return = [] + # Fake search result output + for resource in resources: + to_return.append({'resourceType': service, 'resource': resource}) + return to_return + resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) + return resources + + @staticmethod + def type_to_service(tag_type): + """Returns the SoftLayer service for the given tag_type""" + service = None + if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + return None + + if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + service = 'Network_Application_Delivery_Controller' + elif tag_type == 'GUEST': + service = 'Virtual_Guest' + elif tag_type == 'DEDICATED_HOST': + service = 'Virtual_DedicatedHost' + elif tag_type == 'IMAGE_TEMPLATE': + service = 'Virtual_Guest_Block_Device_Template_Group' + else: + + tag_type = tag_type.lower() + # Sets the First letter, and any letter preceeded by a '_' to uppercase + # HARDWARE -> Hardware, NETWORK_VLAN -> Network_Vlan for example. + service = re.sub(r'(^[a-z]|\_[a-z])', lambda x: x.group().upper(), tag_type) + return service + + @staticmethod + def get_resource_name(resource, tag_type): + """Returns a string that names a resource + + :param dict resource: A SoftLayer datatype for the given tag_type + :param string tag_type: Key name for the tag_type + """ + if tag_type == 'NETWORK_VLAN_FIREWALL': + return resource.get('primaryIpAddress') + elif tag_type == 'NETWORK_VLAN': + return "{} ({})".format(resource.get('vlanNumber'), resource.get('name')) + elif tag_type == 'IMAGE_TEMPLATE' or tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + return resource.get('name') + elif tag_type == 'TICKET': + return resource.get('subjet') + elif tag_type == 'NETWORK_SUBNET': + return resource.get('networkIdentifier') + else: + return resource.get('fullyQualifiedDomainName') + + # @staticmethod + # def type_to_datatype(tag_type): + # """Returns the SoftLayer datatye for the given tag_type""" + # datatye = None + # if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + # return None + + # if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': + # datatye = 'adcLoadBalancers' + # elif tag_type == 'GUEST': + # datatye = 'virtualGuests' + # elif tag_type == 'DEDICATED_HOST': + # datatye = 'dedicatedHosts' + # elif tag_type == 'HARDWARE': + # datatye = 'hardware' + # elif tag_type == 'TICKET': + # datatye = 'openTickets' + # elif tag_type == 'NETWORK_SUBNET': + # datatye = 'subnets' + # elif tag_type == 'NETWORK_VLAN': + # datatye = 'networkVlans' + # elif tag_type == 'NETWORK_VLAN_FIREWALL': + # datatye = 'networkVlans' + # elif tag_type == 'IMAGE_TEMPLATE': + # datatye = 'blockDeviceTemplateGroups' + + # return datatye diff --git a/docCheck.py b/docCheck.py index f6b11ba36..90a8da3f4 100644 --- a/docCheck.py +++ b/docCheck.py @@ -1,94 +1,94 @@ -"""Makes sure all routes have documentation""" -import SoftLayer -from SoftLayer.CLI import routes -from pprint import pprint as pp -import glob -import logging -import os -import sys -import re - -class Checker(): - - def __init__(self): - pass - - def getDocFiles(self, path=None): - files = [] - if path is None: - path = ".{seper}docs{seper}cli".format(seper=os.path.sep) - for file in glob.glob(path + '/*', recursive=True): - if os.path.isdir(file): - files = files + self.getDocFiles(file) - else: - files.append(file) - return files - - def readDocs(self, path=None): - files = self.getDocFiles(path) - commands = {} - click_regex = re.compile(r"\.\. click:: ([a-zA-Z0-9_\.:]*)") - prog_regex = re.compile(r"\W*:prog: (.*)") - - for file in files: - click_line = '' - prog_line = '' - with open(file, 'r') as f: - for line in f: - click_match = re.match(click_regex, line) - prog_match = False - if click_match: - click_line = click_match.group(1) - - # Prog line should always be directly after click line. - prog_match = re.match(prog_regex, f.readline()) - if prog_match: - prog_line = prog_match.group(1).replace(" ", ":") - commands[prog_line] = click_line - click_line = '' - prog_line = '' - # pp(commands) - return commands - - def checkCommand(self, command, documented_commands): - """Sees if a command is documented - - :param tuple command: like the entry in the routes file ('command:action', 'SoftLayer.CLI.module.function') - :param documented_commands: dictionary of commands found to be auto-documented. - """ - - # These commands use a slightly different loader. - ignored = [ - 'virtual:capacity', - 'virtual:placementgroup', - 'object-storage:credential' - ] - if command[0] in ignored: - return True - if documented_commands.get(command[0], False) == command[1]: - return True - return False - - - def main(self, debug=0): - existing_commands = routes.ALL_ROUTES - documented_commands = self.readDocs() - # pp(documented_commands) - exitCode = 0 - for command in existing_commands: - if (command[1].find(":") == -1): # Header commands in the routes file, dont need documentaiton. - continue - else: - if self.checkCommand(command, documented_commands): - if debug: - print("{} is documented".format(command[0])) - - else: - print("===> {} {} IS UNDOCUMENTED <===".format(command[0], command[1])) - exitCode = 1 - sys.exit(exitCode) - - -if __name__ == "__main__": - main = Checker() - main.main() +"""Makes sure all routes have documentation""" +import SoftLayer +from SoftLayer.CLI import routes +from pprint import pprint as pp +import glob +import logging +import os +import sys +import re + +class Checker(): + + def __init__(self): + pass + + def getDocFiles(self, path=None): + files = [] + if path is None: + path = ".{seper}docs{seper}cli".format(seper=os.path.sep) + for file in glob.glob(path + '/*', recursive=True): + if os.path.isdir(file): + files = files + self.getDocFiles(file) + else: + files.append(file) + return files + + def readDocs(self, path=None): + files = self.getDocFiles(path) + commands = {} + click_regex = re.compile(r"\.\. click:: ([a-zA-Z0-9_\.:]*)") + prog_regex = re.compile(r"\W*:prog: (.*)") + + for file in files: + click_line = '' + prog_line = '' + with open(file, 'r') as f: + for line in f: + click_match = re.match(click_regex, line) + prog_match = False + if click_match: + click_line = click_match.group(1) + + # Prog line should always be directly after click line. + prog_match = re.match(prog_regex, f.readline()) + if prog_match: + prog_line = prog_match.group(1).replace(" ", ":") + commands[prog_line] = click_line + click_line = '' + prog_line = '' + # pp(commands) + return commands + + def checkCommand(self, command, documented_commands): + """Sees if a command is documented + + :param tuple command: like the entry in the routes file ('command:action', 'SoftLayer.CLI.module.function') + :param documented_commands: dictionary of commands found to be auto-documented. + """ + + # These commands use a slightly different loader. + ignored = [ + 'virtual:capacity', + 'virtual:placementgroup', + 'object-storage:credential' + ] + if command[0] in ignored: + return True + if documented_commands.get(command[0], False) == command[1]: + return True + return False + + + def main(self, debug=0): + existing_commands = routes.ALL_ROUTES + documented_commands = self.readDocs() + # pp(documented_commands) + exitCode = 0 + for command in existing_commands: + if (command[1].find(":") == -1): # Header commands in the routes file, dont need documentaiton. + continue + else: + if self.checkCommand(command, documented_commands): + if debug: + print("{} is documented".format(command[0])) + + else: + print("===> {} {} IS UNDOCUMENTED <===".format(command[0], command[1])) + exitCode = 1 + sys.exit(exitCode) + + +if __name__ == "__main__": + main = Checker() + main.main() diff --git a/docs/cli/nas.rst b/docs/cli/nas.rst index 024744919..2e0f7079e 100644 --- a/docs/cli/nas.rst +++ b/docs/cli/nas.rst @@ -1,12 +1,12 @@ -.. _cli_nas: - -NAS Commands -============ - -.. click:: SoftLayer.CLI.nas.list:cli - :prog: nas list - :show-nested: - -.. click:: SoftLayer.CLI.nas.credentials:cli - :prog: nas credentials +.. _cli_nas: + +NAS Commands +============ + +.. click:: SoftLayer.CLI.nas.list:cli + :prog: nas list + :show-nested: + +.. click:: SoftLayer.CLI.nas.credentials:cli + :prog: nas credentials :show-nested: \ No newline at end of file diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index a5a99b694..d997760de 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -1,30 +1,30 @@ -.. _cli_tags: - -Tag Commands -============ - -These commands will allow you to interact with the **IMS** provier tagging service. The `IBM Global Search and Tagging API `_ can be used to interact with both the **GHOST** provider and **IMS** provider. The **GHOST** provider will handle tags for things outside of the Classic Infrastructure (aka SoftLayer) space. - -.. click:: SoftLayer.CLI.tags.list:cli - :prog: tags list - :show-nested: - -.. click:: SoftLayer.CLI.tags.set:cli - :prog: tags set - :show-nested: - -.. click:: SoftLayer.CLI.tags.details:cli - :prog: tags details - :show-nested: - -.. click:: SoftLayer.CLI.tags.delete:cli - :prog: tags delete - :show-nested: - -.. click:: SoftLayer.CLI.tags.taggable:cli - :prog: tags taggable - :show-nested: - -.. click:: SoftLayer.CLI.tags.cleanup:cli - :prog: tags cleanup - :show-nested: +.. _cli_tags: + +Tag Commands +============ + +These commands will allow you to interact with the **IMS** provier tagging service. The `IBM Global Search and Tagging API `_ can be used to interact with both the **GHOST** provider and **IMS** provider. The **GHOST** provider will handle tags for things outside of the Classic Infrastructure (aka SoftLayer) space. + +.. click:: SoftLayer.CLI.tags.list:cli + :prog: tags list + :show-nested: + +.. click:: SoftLayer.CLI.tags.set:cli + :prog: tags set + :show-nested: + +.. click:: SoftLayer.CLI.tags.details:cli + :prog: tags details + :show-nested: + +.. click:: SoftLayer.CLI.tags.delete:cli + :prog: tags delete + :show-nested: + +.. click:: SoftLayer.CLI.tags.taggable:cli + :prog: tags taggable + :show-nested: + +.. click:: SoftLayer.CLI.tags.cleanup:cli + :prog: tags cleanup + :show-nested: diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 364201181..12fc85768 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -1,113 +1,113 @@ -""" - SoftLayer.tests.CLI.modules.tag_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Tests for the user cli command -""" -from unittest import mock as mock - -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer import testing - - -class TagCLITests(testing.TestCase): - - def test_list(self): - result = self.run_command(['tags', 'list']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assertIn('coreos', result.output) - - def test_list_detail(self): - result = self.run_command(['tags', 'list', '-d']) - self.assert_no_fail(result) - self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject - # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) - - def test_list_detail_ungettable(self): - mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') - mock.side_effect = SoftLayerAPIError(404, "TEST ERROR") - result = self.run_command(['tags', 'list', '-d']) - self.assert_no_fail(result) - self.assertIn("TEST ERROR", result.output) # From fixtures/virutal_guest.getObject - # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) - - @mock.patch('SoftLayer.CLI.tags.set.click') - def test_set_tags(self, click): - result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) - click.secho.assert_called_with('Set tags successfully', fg='green') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) - - @mock.patch('SoftLayer.CLI.tags.set.click') - def test_set_tags_failure(self, click): - mock = self.set_mock('SoftLayer_Tag', 'setTags') - mock.return_value = False - result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) - click.secho.assert_called_with('Failed to set tags', fg='red') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) - - def test_details_by_name(self): - tag_name = 'bs_test_instance' - result = self.run_command(['tags', 'details', tag_name]) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=(tag_name,)) - - def test_details_by_id(self): - tag_id = '1286571' - result = self.run_command(['tags', 'details', tag_id]) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) - - def test_deleteTags_by_name(self): - result = self.run_command(['tags', 'delete', 'test']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('test',)) - - def test_deleteTags_by_id(self): - result = self.run_command(['tags', 'delete', '123456']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier='123456') - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('bs_test_instance',)) - - def test_deleteTags_by_number_name(self): - result = self.run_command(['tags', 'delete', '123456', '--name']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) - - @mock.patch('SoftLayer.CLI.tags.delete.click') - def test_deleteTags_fail(self, click): - mock = self.set_mock('SoftLayer_Tag', 'deleteTag') - mock.return_value = False - result = self.run_command(['tags', 'delete', '123456', '--name']) - click.secho.assert_called_with('Failed to remove tag 123456', fg='red') - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) - - def test_taggable(self): - result = self.run_command(['tags', 'taggable']) - self.assert_no_fail(result) - self.assertIn('"host14.vmware.test.com', result.output) - self.assert_called_with('SoftLayer_Tag', 'getAllTagTypes') - self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) - - def test_cleanup(self): - result = self.run_command(['tags', 'cleanup']) - self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('coreos',)) - - def test_cleanup_dry(self): - result = self.run_command(['tags', 'cleanup', '-d']) - self.assert_no_fail(result) - self.assertIn('(Dry Run)', result.output) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assertEqual([], self.calls(service='SoftLayer_Tag', method='deleteTag')) +""" + SoftLayer.tests.CLI.modules.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from unittest import mock as mock + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer import testing + + +class TagCLITests(testing.TestCase): + + def test_list(self): + result = self.run_command(['tags', 'list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('coreos', result.output) + + def test_list_detail(self): + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn('"vs-test1.test.sftlyr.ws', result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + + def test_list_detail_ungettable(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.side_effect = SoftLayerAPIError(404, "TEST ERROR") + result = self.run_command(['tags', 'list', '-d']) + self.assert_no_fail(result) + self.assertIn("TEST ERROR", result.output) # From fixtures/virutal_guest.getObject + # self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=1286571) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags(self, click): + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Set tags successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) + + @mock.patch('SoftLayer.CLI.tags.set.click') + def test_set_tags_failure(self, click): + mock = self.set_mock('SoftLayer_Tag', 'setTags') + mock.return_value = False + result = self.run_command(['tags', 'set', '--tags=tag1,tag2', '--key-name=GUEST', '--resource-id=100']) + click.secho.assert_called_with('Failed to set tags', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) + + def test_details_by_name(self): + tag_name = 'bs_test_instance' + result = self.run_command(['tags', 'details', tag_name]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=(tag_name,)) + + def test_details_by_id(self): + tag_id = '1286571' + result = self.run_command(['tags', 'details', tag_id]) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_deleteTags_by_name(self): + result = self.run_command(['tags', 'delete', 'test']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('test',)) + + def test_deleteTags_by_id(self): + result = self.run_command(['tags', 'delete', '123456']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier='123456') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('bs_test_instance',)) + + def test_deleteTags_by_number_name(self): + result = self.run_command(['tags', 'delete', '123456', '--name']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + @mock.patch('SoftLayer.CLI.tags.delete.click') + def test_deleteTags_fail(self, click): + mock = self.set_mock('SoftLayer_Tag', 'deleteTag') + mock.return_value = False + result = self.run_command(['tags', 'delete', '123456', '--name']) + click.secho.assert_called_with('Failed to remove tag 123456', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('123456',)) + + def test_taggable(self): + result = self.run_command(['tags', 'taggable']) + self.assert_no_fail(result) + self.assertIn('"host14.vmware.test.com', result.output) + self.assert_called_with('SoftLayer_Tag', 'getAllTagTypes') + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_cleanup(self): + result = self.run_command(['tags', 'cleanup']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'deleteTag', args=('coreos',)) + + def test_cleanup_dry(self): + result = self.run_command(['tags', 'cleanup', '-d']) + self.assert_no_fail(result) + self.assertIn('(Dry Run)', result.output) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assertEqual([], self.calls(service='SoftLayer_Tag', method='deleteTag')) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 67c817a6f..51b0d2198 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -1,208 +1,208 @@ -""" - SoftLayer.tests.managers.tag_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" - -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers import tags -from SoftLayer import testing - - -class TagTests(testing.TestCase): - - def set_up(self): - self.tag_manager = tags.TagManager(self.client) - self.test_mask = "mask[id]" - - def test_list_tags(self): - result = self.tag_manager.list_tags() - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') - self.assertIn('attached', result.keys()) - self.assertIn('unattached', result.keys()) - - def test_list_tags_mask(self): - result = self.tag_manager.list_tags(mask=self.test_mask) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) - self.assertIn('attached', result.keys()) - self.assertIn('unattached', result.keys()) - - def test_unattached_tags(self): - result = self.tag_manager.get_unattached_tags() - self.assertEqual('coreos', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=None) - - def test_unattached_tags_mask(self): - result = self.tag_manager.get_unattached_tags(mask=self.test_mask) - self.assertEqual('coreos', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) - - def test_attached_tags(self): - result = self.tag_manager.get_attached_tags() - self.assertEqual('bs_test_instance', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=None) - - def test_attached_tags_mask(self): - result = self.tag_manager.get_attached_tags(mask=self.test_mask) - self.assertEqual('bs_test_instance', result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) - - def test_get_tag_references(self): - tag_id = 1286571 - result = self.tag_manager.get_tag_references(tag_id) - self.assertEqual(tag_id, result[0].get('tagId')) - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id) - - def test_get_tag_references_mask(self): - tag_id = 1286571 - result = self.tag_manager.get_tag_references(tag_id, mask=self.test_mask) - self.assertEqual(tag_id, result[0].get('tagId')) - self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id, mask=self.test_mask) - - def test_reference_lookup_hardware(self): - resource_id = 12345 - tag_type = 'HARDWARE' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) - - def test_reference_lookup_guest(self): - resource_id = 12345 - tag_type = 'GUEST' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) - - def test_reference_lookup_app_delivery(self): - resource_id = 12345 - tag_type = 'APPLICATION_DELIVERY_CONTROLLER' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', - 'getObject', identifier=resource_id) - - def test_reference_lookup_dedicated(self): - resource_id = 12345 - tag_type = 'DEDICATED_HOST' - - self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) - - def test_reference_lookup_document(self): - resource_id = 12345 - tag_type = 'ACCOUNT_DOCUMENT' - - exception = self.assertRaises( - SoftLayerAPIError, - self.tag_manager.reference_lookup, - resource_id, - tag_type - ) - self.assertEqual(exception.faultCode, 404) - self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") - - def test_set_tags(self): - tags = "tag1,tag2" - key_name = "GUEST" - resource_id = 100 - - self.tag_manager.set_tags(tags, key_name, resource_id) - self.assert_called_with('SoftLayer_Tag', 'setTags') - - def test_get_tag(self): - tag_id = 1286571 - result = self.tag_manager.get_tag(tag_id) - self.assertEqual(tag_id, result.get('id')) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) - - def test_get_tag_mask(self): - tag_id = 1286571 - result = self.tag_manager.get_tag(tag_id, mask=self.test_mask) - self.assertEqual(tag_id, result.get('id')) - self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id, mask=self.test_mask) - - def test_get_tag_by_name(self): - tag_name = 'bs_test_instance' - result = self.tag_manager.get_tag_by_name(tag_name) - args = (tag_name,) - self.assertEqual(tag_name, result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=args) - - def test_get_tag_by_name_mask(self): - tag_name = 'bs_test_instance' - result = self.tag_manager.get_tag_by_name(tag_name, mask=self.test_mask) - args = (tag_name,) - self.assertEqual(tag_name, result[0].get('name')) - self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) - - def test_taggable_by_type_main(self): - result = self.tag_manager.taggable_by_type("HARDWARE") - self.assertEqual("SoftLayer_Hardware", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) - - def test_taggable_by_type_ticket(self): - mock = self.set_mock('SoftLayer_Search', 'advancedSearch') - mock.return_value = [ - { - "resourceType": "SoftLayer_Ticket", - "resource": { - "domain": "vmware.test.com", - } - } - ] - - result = self.tag_manager.taggable_by_type("TICKET") - self.assertEqual("SoftLayer_Ticket", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Search', 'advancedSearch', - args=('_objectType:SoftLayer_Ticket status.name: open',)) - - def test_taggable_by_type_image_template(self): - result = self.tag_manager.taggable_by_type("IMAGE_TEMPLATE") - self.assertEqual("Virtual_Guest_Block_Device_Template_Group", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups') - - def test_taggable_by_type_network_subnet(self): - result = self.tag_manager.taggable_by_type("NETWORK_SUBNET") - self.assertEqual("Network_Subnet", result[0].get('resourceType')) - self.assert_called_with('SoftLayer_Account', 'getSubnets') - - def test_type_to_service(self): - in_out = [ - {'input': 'ACCOUNT_DOCUMENT', 'output': None}, - {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': 'Network_Application_Delivery_Controller'}, - {'input': 'GUEST', 'output': 'Virtual_Guest'}, - {'input': 'DEDICATED_HOST', 'output': 'Virtual_DedicatedHost'}, - {'input': 'IMAGE_TEMPLATE', 'output': 'Virtual_Guest_Block_Device_Template_Group'}, - {'input': 'HARDWARE', 'output': 'Hardware'}, - {'input': 'NETWORK_VLAN', 'output': 'Network_Vlan'}, - ] - - for test in in_out: - result = self.tag_manager.type_to_service(test.get('input')) - self.assertEqual(test.get('output'), result) - - def test_get_resource_name(self): - resource = { - 'primaryIpAddress': '4.4.4.4', - 'vlanNumber': '12345', - 'name': 'testName', - 'subject': 'TEST SUBJECT', - 'networkIdentifier': '127.0.0.1', - 'fullyQualifiedDomainName': 'test.test.com' - } - in_out = [ - {'input': 'NETWORK_VLAN_FIREWALL', 'output': resource.get('primaryIpAddress')}, - {'input': 'NETWORK_VLAN', 'output': "{} ({})".format(resource.get('vlanNumber'), resource.get('name'))}, - {'input': 'IMAGE_TEMPLATE', 'output': resource.get('name')}, - {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': resource.get('name')}, - {'input': 'TICKET', 'output': resource.get('subjet')}, - {'input': 'NETWORK_SUBNET', 'output': resource.get('networkIdentifier')}, - {'input': 'HARDWARE', 'output': resource.get('fullyQualifiedDomainName')}, - ] - - for test in in_out: - result = self.tag_manager.get_resource_name(resource, test.get('input')) - self.assertEqual(test.get('output'), result) +""" + SoftLayer.tests.managers.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers import tags +from SoftLayer import testing + + +class TagTests(testing.TestCase): + + def set_up(self): + self.tag_manager = tags.TagManager(self.client) + self.test_mask = "mask[id]" + + def test_list_tags(self): + result = self.tag_manager.list_tags() + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser') + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser') + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_list_tags_mask(self): + result = self.tag_manager.list_tags(mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + self.assertIn('attached', result.keys()) + self.assertIn('unattached', result.keys()) + + def test_unattached_tags(self): + result = self.tag_manager.get_unattached_tags() + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=None) + + def test_unattached_tags_mask(self): + result = self.tag_manager.get_unattached_tags(mask=self.test_mask) + self.assertEqual('coreos', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', mask=self.test_mask) + + def test_attached_tags(self): + result = self.tag_manager.get_attached_tags() + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=None) + + def test_attached_tags_mask(self): + result = self.tag_manager.get_attached_tags(mask=self.test_mask) + self.assertEqual('bs_test_instance', result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', mask=self.test_mask) + + def test_get_tag_references(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id) + + def test_get_tag_references_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag_references(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result[0].get('tagId')) + self.assert_called_with('SoftLayer_Tag', 'getReferences', identifier=tag_id, mask=self.test_mask) + + def test_reference_lookup_hardware(self): + resource_id = 12345 + tag_type = 'HARDWARE' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) + + def test_reference_lookup_guest(self): + resource_id = 12345 + tag_type = 'GUEST' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=resource_id) + + def test_reference_lookup_app_delivery(self): + resource_id = 12345 + tag_type = 'APPLICATION_DELIVERY_CONTROLLER' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', + 'getObject', identifier=resource_id) + + def test_reference_lookup_dedicated(self): + resource_id = 12345 + tag_type = 'DEDICATED_HOST' + + self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Virtual_DedicatedHost', 'getObject', identifier=resource_id) + + def test_reference_lookup_document(self): + resource_id = 12345 + tag_type = 'ACCOUNT_DOCUMENT' + + exception = self.assertRaises( + SoftLayerAPIError, + self.tag_manager.reference_lookup, + resource_id, + tag_type + ) + self.assertEqual(exception.faultCode, 404) + self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") + + def test_set_tags(self): + tags = "tag1,tag2" + key_name = "GUEST" + resource_id = 100 + + self.tag_manager.set_tags(tags, key_name, resource_id) + self.assert_called_with('SoftLayer_Tag', 'setTags') + + def test_get_tag(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) + + def test_get_tag_mask(self): + tag_id = 1286571 + result = self.tag_manager.get_tag(tag_id, mask=self.test_mask) + self.assertEqual(tag_id, result.get('id')) + self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id, mask=self.test_mask) + + def test_get_tag_by_name(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', args=args) + + def test_get_tag_by_name_mask(self): + tag_name = 'bs_test_instance' + result = self.tag_manager.get_tag_by_name(tag_name, mask=self.test_mask) + args = (tag_name,) + self.assertEqual(tag_name, result[0].get('name')) + self.assert_called_with('SoftLayer_Tag', 'getTagByTagName', mask=self.test_mask, args=args) + + def test_taggable_by_type_main(self): + result = self.tag_manager.taggable_by_type("HARDWARE") + self.assertEqual("SoftLayer_Hardware", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', args=('_objectType:SoftLayer_Hardware',)) + + def test_taggable_by_type_ticket(self): + mock = self.set_mock('SoftLayer_Search', 'advancedSearch') + mock.return_value = [ + { + "resourceType": "SoftLayer_Ticket", + "resource": { + "domain": "vmware.test.com", + } + } + ] + + result = self.tag_manager.taggable_by_type("TICKET") + self.assertEqual("SoftLayer_Ticket", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Search', 'advancedSearch', + args=('_objectType:SoftLayer_Ticket status.name: open',)) + + def test_taggable_by_type_image_template(self): + result = self.tag_manager.taggable_by_type("IMAGE_TEMPLATE") + self.assertEqual("Virtual_Guest_Block_Device_Template_Group", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getPrivateBlockDeviceTemplateGroups') + + def test_taggable_by_type_network_subnet(self): + result = self.tag_manager.taggable_by_type("NETWORK_SUBNET") + self.assertEqual("Network_Subnet", result[0].get('resourceType')) + self.assert_called_with('SoftLayer_Account', 'getSubnets') + + def test_type_to_service(self): + in_out = [ + {'input': 'ACCOUNT_DOCUMENT', 'output': None}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': 'Network_Application_Delivery_Controller'}, + {'input': 'GUEST', 'output': 'Virtual_Guest'}, + {'input': 'DEDICATED_HOST', 'output': 'Virtual_DedicatedHost'}, + {'input': 'IMAGE_TEMPLATE', 'output': 'Virtual_Guest_Block_Device_Template_Group'}, + {'input': 'HARDWARE', 'output': 'Hardware'}, + {'input': 'NETWORK_VLAN', 'output': 'Network_Vlan'}, + ] + + for test in in_out: + result = self.tag_manager.type_to_service(test.get('input')) + self.assertEqual(test.get('output'), result) + + def test_get_resource_name(self): + resource = { + 'primaryIpAddress': '4.4.4.4', + 'vlanNumber': '12345', + 'name': 'testName', + 'subject': 'TEST SUBJECT', + 'networkIdentifier': '127.0.0.1', + 'fullyQualifiedDomainName': 'test.test.com' + } + in_out = [ + {'input': 'NETWORK_VLAN_FIREWALL', 'output': resource.get('primaryIpAddress')}, + {'input': 'NETWORK_VLAN', 'output': "{} ({})".format(resource.get('vlanNumber'), resource.get('name'))}, + {'input': 'IMAGE_TEMPLATE', 'output': resource.get('name')}, + {'input': 'APPLICATION_DELIVERY_CONTROLLER', 'output': resource.get('name')}, + {'input': 'TICKET', 'output': resource.get('subjet')}, + {'input': 'NETWORK_SUBNET', 'output': resource.get('networkIdentifier')}, + {'input': 'HARDWARE', 'output': resource.get('fullyQualifiedDomainName')}, + ] + + for test in in_out: + result = self.tag_manager.get_resource_name(resource, test.get('input')) + self.assertEqual(test.get('output'), result) From 252fd02881f3e649c3f8d6c3e6d8d9028f5fe916 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:04:40 -0600 Subject: [PATCH 0267/1050] Added mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..0cbadf756 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Christopher Gallo From 9132182ad7809241092935b6735a8262a6b3ab51 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:05:53 -0600 Subject: [PATCH 0268/1050] going to v6.0.1 because the readme was broken so now I have to make a new version --- CHANGELOG.md | 5 +++-- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 799351a6f..6b6f55a71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [6.0.0] - 2022-03-11 +## [6.0.1] - 2022-03-11 ## What's Changed @@ -12,8 +12,9 @@ * fix to errors in slcli hw create-options by @caberos in https://github.com/softlayer/softlayer-python/pull/1594 -**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.0 +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v5.9.9...v6.0.1 +6.0.0 was skipped. ## [5.9.9] - 2022-02-04 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 38a8289bf..54e4dfd22 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.0.0' +VERSION = 'v6.0.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 39bf801f6..0138ba9de 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.0.0', + version='6.0.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', From 37be462aa478037ccc840d2fb9f8543bd4a42727 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 11 Mar 2022 15:14:37 -0600 Subject: [PATCH 0269/1050] added gitattributes to kinda enforce line endings --- .gitattributes | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..05041c45f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Language aware diff headers +# https://tekin.co.uk/2020/10/better-git-diff-output-for-ruby-python-elixir-and-more +# https://gist.github.com/tekin/12500956bd56784728e490d8cef9cb81 +*.css diff=css +*.html diff=html +*.py diff=python +*.md diff=markdown + + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary From f543f3d2dc45229cd705be897f11c5f9c169bd39 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Mar 2022 10:16:00 -0400 Subject: [PATCH 0270/1050] fix the code review comments --- SoftLayer/CLI/hardware/monitoring.py | 10 +++++----- SoftLayer/CLI/virt/monitoring.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SoftLayer/CLI/hardware/monitoring.py b/SoftLayer/CLI/hardware/monitoring.py index 81640f3e5..58f4f614f 100644 --- a/SoftLayer/CLI/hardware/monitoring.py +++ b/SoftLayer/CLI/hardware/monitoring.py @@ -22,12 +22,12 @@ def cli(env, identifier): monitoring = hardware.get_hardware(identifier) - table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) - table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) - table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) - table.add_row(['location', monitoring['datacenter']['longName']]) + table.add_row(['Domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['Public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['Private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['Location', monitoring['datacenter']['longName']]) - monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + monitoring_table = formatting.Table(['Id', 'IpAddress', 'Status', 'Type', 'Notify']) for monitor in monitoring['networkMonitors']: monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) diff --git a/SoftLayer/CLI/virt/monitoring.py b/SoftLayer/CLI/virt/monitoring.py index 4e76549cf..27e16d35a 100644 --- a/SoftLayer/CLI/virt/monitoring.py +++ b/SoftLayer/CLI/virt/monitoring.py @@ -1,4 +1,4 @@ -"""Get monitoring for a vSI device.""" +"""Get monitoring for a VSI device.""" # :license: MIT, see LICENSE for more details. import click @@ -22,12 +22,12 @@ def cli(env, identifier): monitoring = vsi.get_instance(identifier) - table.add_row(['domain', monitoring.get('fullyQualifiedDomainName')]) - table.add_row(['public Ip', monitoring.get('primaryIpAddress')]) - table.add_row(['private Ip', monitoring.get('primaryBackendIpAddress')]) - table.add_row(['location', monitoring['datacenter']['longName']]) + table.add_row(['Domain', monitoring.get('fullyQualifiedDomainName')]) + table.add_row(['Public Ip', monitoring.get('primaryIpAddress')]) + table.add_row(['Private Ip', monitoring.get('primaryBackendIpAddress')]) + table.add_row(['Location', monitoring['datacenter']['longName']]) - monitoring_table = formatting.Table(['Id', 'ipAddress', 'status', 'type', 'notify']) + monitoring_table = formatting.Table(['Id', 'IpAddress', 'Status', 'Type', 'Notify']) for monitor in monitoring['networkMonitors']: monitoring_table.add_row([monitor.get('id'), monitor.get('ipAddress'), monitor.get('status'), monitor['queryType']['name'], monitor['responseAction']['actionDescription']]) From 5fef78815ae08ed536948db97b7ba92eac55cef3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 21 Mar 2022 12:12:15 -0400 Subject: [PATCH 0271/1050] When listing datacenters/pods, mark those that are closing soon. --- SoftLayer/CLI/hardware/create_options.py | 30 ++++++++++++++++++++++-- SoftLayer/CLI/virt/create_options.py | 30 ++++++++++++++++++++++-- SoftLayer/managers/network.py | 8 +++---- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 646d37c09..5a59bf696 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import formatting from SoftLayer.managers import account from SoftLayer.managers import hardware +from SoftLayer.managers import network @click.command() @@ -22,14 +23,39 @@ def cli(env, prices, location=None): account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) routers = account_manager.get_routers(location=location) + network_manager = network.NetworkManager(env.client) + + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: - dc_table.add_row([location_info['name'], location_info['key']]) + closure = [] + for pod in pods: + if ((location_info['key'] in str(pod['name']))): + closure.append(pod['name']) + + if len(closure) == 0: + closure = '' + else: + closure = 'closed soon: %s' % (str(closure)) + dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) tables.append(dc_table) tables.append(_preset_prices_table(options['sizes'], prices)) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index a3ee24314..1db69fe68 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. # pylint: disable=too-many-statements import click +from SoftLayer.managers import network import SoftLayer from SoftLayer.CLI import environment @@ -22,16 +23,41 @@ def cli(env, vsi_type, prices, location=None): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) + network_manager = network.NetworkManager(env.client) options = vsi.get_create_options(vsi_type, location) + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + tables = [] # Datacenters - dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: - dc_table.add_row([location_info['name'], location_info['key']]) + closure = [] + for pod in pods: + if ((location_info['key'] in str(pod['name']))): + closure.append(pod['name']) + + if len(closure) == 0: + closure = '' + else: + closure = 'closed soon: %s' % (str(closure)) + dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) tables.append(dc_table) if vsi_type == 'CLOUD_SERVER': diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6638a29d3..6eaabfc44 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -779,16 +779,16 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_pods(self, datacenter=None): + def get_pods(self, mask=None, filter=None, datacenter=None): """Calls SoftLayer_Network_Pod::getAllObjects() returns list of all network pods and their routers. """ - _filter = None + if datacenter: - _filter = {"datacenterName": {"operation": datacenter}} + filter = {"datacenterName": {"operation": datacenter}} - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=filter) def get_list_datacenter(self): """Calls SoftLayer_Location::getDatacenters() From c690793800ae5ae903eba3de7283dac11cf48e7a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:26:47 -0400 Subject: [PATCH 0272/1050] fix the team code review comments --- SoftLayer/CLI/hardware/create_options.py | 27 ++++++------------------ SoftLayer/CLI/virt/create_options.py | 26 ++++++----------------- SoftLayer/managers/network.py | 27 +++++++++++++++++++++--- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 5a59bf696..556c3af75 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -25,24 +25,12 @@ def cli(env, prices, location=None): routers = account_manager.get_routers(location=location) network_manager = network.NetworkManager(env.client) - closing_filter = { - 'capabilities': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] - }, - 'name': { - 'operation': 'orderBy', - 'options': [{'name': 'sort', 'value': ['DESC']}] - } - } - - pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, - backendRouterName, frontendRouterName]""" - pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + + pods = network_manager.get_closed_pods() tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'Note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: @@ -51,11 +39,10 @@ def cli(env, prices, location=None): if ((location_info['key'] in str(pod['name']))): closure.append(pod['name']) - if len(closure) == 0: - closure = '' - else: - closure = 'closed soon: %s' % (str(closure)) - dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + dc_table.add_row([location_info['name'], location_info['key'], notes]) tables.append(dc_table) tables.append(_preset_prices_table(options['sizes'], prices)) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 1db69fe68..644e9c900 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -26,25 +26,12 @@ def cli(env, vsi_type, prices, location=None): network_manager = network.NetworkManager(env.client) options = vsi.get_create_options(vsi_type, location) - closing_filter = { - 'capabilities': { - 'operation': 'in', - 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] - }, - 'name': { - 'operation': 'orderBy', - 'options': [{'name': 'sort', 'value': ['DESC']}] - } - } - - pods_mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, - backendRouterName, frontendRouterName]""" - pods = network_manager.get_pods(mask=pods_mask, filter=closing_filter) + pods = network_manager.get_closed_pods() tables = [] # Datacenters - dc_table = formatting.Table(['Datacenter', 'Value', 'note'], title="Datacenters") + dc_table = formatting.Table(['Datacenter', 'Value', 'Note'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' for location_info in options['locations']: @@ -53,11 +40,10 @@ def cli(env, vsi_type, prices, location=None): if ((location_info['key'] in str(pod['name']))): closure.append(pod['name']) - if len(closure) == 0: - closure = '' - else: - closure = 'closed soon: %s' % (str(closure)) - dc_table.add_row([location_info['name'], location_info['key'], str(closure)]) + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) + dc_table.add_row([location_info['name'], location_info['key'], notes]) tables.append(dc_table) if vsi_type == 'CLOUD_SERVER': diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6eaabfc44..0f550ec3d 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -779,16 +779,17 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_pods(self, mask=None, filter=None, datacenter=None): + def get_pods(self, datacenter=None): """Calls SoftLayer_Network_Pod::getAllObjects() returns list of all network pods and their routers. """ + _filter = None if datacenter: - filter = {"datacenterName": {"operation": datacenter}} + _filter = {"datacenterName": {"operation": datacenter}} - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=filter) + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) def get_list_datacenter(self): """Calls SoftLayer_Location::getDatacenters() @@ -803,3 +804,23 @@ def get_routers(self, identifier): returns all routers locations. """ return self.client.call('SoftLayer_Location_Datacenter', 'getHardwareRouters', id=identifier) + + def get_closed_pods(self): + """Calls SoftLayer_Network_Pod::getAllObjects() + + returns list of all closing network pods. + """ + closing_filter = { + 'capabilities': { + 'operation': 'in', + 'options': [{'name': 'data', 'value': ['CLOSURE_ANNOUNCED']}] + }, + 'name': { + 'operation': 'orderBy', + 'options': [{'name': 'sort', 'value': ['DESC']}] + } + } + + mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, + backendRouterName, frontendRouterName]""" + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) From 0567276624979bbee362b5bd00872897c23b8e5b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:35:27 -0400 Subject: [PATCH 0273/1050] fix the tox analysis --- SoftLayer/CLI/hardware/create_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 556c3af75..b7183758b 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -25,8 +25,8 @@ def cli(env, prices, location=None): routers = account_manager.get_routers(location=location) network_manager = network.NetworkManager(env.client) - pods = network_manager.get_closed_pods() + tables = [] # Datacenters From 68fa92e770550471045f89b64cf6be7e5a47f977 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Mar 2022 08:40:00 -0400 Subject: [PATCH 0274/1050] fix the tox analysis --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/CLI/virt/create_options.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index b7183758b..cf09971da 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -36,7 +36,7 @@ def cli(env, prices, location=None): for location_info in options['locations']: closure = [] for pod in pods: - if ((location_info['key'] in str(pod['name']))): + if location_info['key'] in str(pod['name']): closure.append(pod['name']) notes = '-' diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 644e9c900..ac84eda64 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -37,7 +37,7 @@ def cli(env, vsi_type, prices, location=None): for location_info in options['locations']: closure = [] for pod in pods: - if ((location_info['key'] in str(pod['name']))): + if location_info['key'] in str(pod['name']): closure.append(pod['name']) notes = '-' From 6937a461fa826c21d5d44765c020cf131cc28713 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Mar 2022 12:08:04 -0400 Subject: [PATCH 0275/1050] Add an orderBy filter to slcli vlan list --- SoftLayer/managers/network.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 6638a29d3..bcb73f786 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -506,7 +506,7 @@ def list_subnets(self, identifier=None, datacenter=None, version=0, kwargs['iter'] = True return self.client.call('Account', 'getSubnets', **kwargs) - def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): + def list_vlans(self, datacenter=None, vlan_number=None, name=None, limit=100, **kwargs): """Display a list of all VLANs on the account. This provides a quick overview of all VLANs including information about @@ -523,6 +523,8 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): """ _filter = utils.NestedDict(kwargs.get('filter') or {}) + _filter['networkVlans']['id'] = utils.query_filter_orderby() + if vlan_number: _filter['networkVlans']['vlanNumber'] = ( utils.query_filter(vlan_number)) @@ -540,7 +542,7 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, **kwargs): kwargs['mask'] = DEFAULT_VLAN_MASK kwargs['iter'] = True - return self.account.getNetworkVlans(**kwargs) + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) def list_securitygroups(self, **kwargs): """List security groups.""" From 9083aba4b4884cc2df0dd27e37ae01466854a7d6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Mar 2022 18:28:34 -0400 Subject: [PATCH 0276/1050] fix the team code review comments --- SoftLayer/CLI/order/package_locations.py | 16 ++++++++++++++-- tests/CLI/modules/order_tests.py | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/package_locations.py b/SoftLayer/CLI/order/package_locations.py index 9f8ffb655..984a7f276 100644 --- a/SoftLayer/CLI/order/package_locations.py +++ b/SoftLayer/CLI/order/package_locations.py @@ -4,9 +4,10 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers import network from SoftLayer.managers import ordering -COLUMNS = ['id', 'dc', 'description', 'keyName'] +COLUMNS = ['id', 'dc', 'description', 'keyName', 'note'] @click.command() @@ -18,15 +19,26 @@ def cli(env, package_keyname): Use the location Key Name to place orders """ manager = ordering.OrderingManager(env.client) + network_manager = network.NetworkManager(env.client) + + pods = network_manager.get_closed_pods() table = formatting.Table(COLUMNS) locations = manager.package_locations(package_keyname) for region in locations: for datacenter in region['locations']: + closure = [] + for pod in pods: + if datacenter['location']['name'] in str(pod['name']): + closure.append(pod['name']) + + notes = '-' + if len(closure) > 0: + notes = 'closed soon: %s' % (', '.join(closure)) table.add_row([ datacenter['location']['id'], datacenter['location']['name'], region['description'], - region['keyname'] + region['keyname'], notes ]) env.fout(table) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 0e8878093..a8040bafa 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -309,7 +309,8 @@ def test_location_list(self): result = self.run_command(['order', 'package-locations', 'package']) self.assert_no_fail(result) expected_results = [ - {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', 'keyName': 'WASHINGTON07'} + {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', + 'keyName': 'WASHINGTON07','note': 'closed soon: wdc07.pod01'} ] print("FUCK") print(result.output) From 3c9cf602a1082b93ca8bc9a4daf05379becd5b84 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 08:22:30 -0400 Subject: [PATCH 0277/1050] fix the team code review comments --- tests/CLI/modules/order_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a8040bafa..490362b99 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -310,7 +310,7 @@ def test_location_list(self): self.assert_no_fail(result) expected_results = [ {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', - 'keyName': 'WASHINGTON07','note': 'closed soon: wdc07.pod01'} + 'keyName': 'WASHINGTON07', 'note': 'closed soon: wdc07.pod01'} ] print("FUCK") print(result.output) From b8ebc9a625a582843f6f54f1b35d7d34cbee1043 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 10:35:25 -0400 Subject: [PATCH 0278/1050] fix the team code review comments --- SoftLayer/CLI/order/package_locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/order/package_locations.py b/SoftLayer/CLI/order/package_locations.py index 984a7f276..2bddbf66f 100644 --- a/SoftLayer/CLI/order/package_locations.py +++ b/SoftLayer/CLI/order/package_locations.py @@ -7,7 +7,7 @@ from SoftLayer.managers import network from SoftLayer.managers import ordering -COLUMNS = ['id', 'dc', 'description', 'keyName', 'note'] +COLUMNS = ['id', 'dc', 'description', 'keyName', 'Note'] @click.command() From 79a16e5cd123c94c55a5a0772dd59272ec681016 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Mar 2022 15:38:59 -0400 Subject: [PATCH 0279/1050] fix the team code review comments --- tests/CLI/modules/order_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 490362b99..be03bbd17 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -310,10 +310,9 @@ def test_location_list(self): self.assert_no_fail(result) expected_results = [ {'id': 2017603, 'dc': 'wdc07', 'description': 'WDC07 - Washington, DC', - 'keyName': 'WASHINGTON07', 'note': 'closed soon: wdc07.pod01'} + 'keyName': 'WASHINGTON07', 'Note': 'closed soon: wdc07.pod01'} ] - print("FUCK") - print(result.output) + self.assertEqual(expected_results, json.loads(result.output)) def test_quote_verify(self): From 2af5f07d0aef1b34b6261df95c6a1e405c529a9d Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Mar 2022 11:54:07 -0400 Subject: [PATCH 0280/1050] Add a warning if user orders in a POD that is being closed --- SoftLayer/CLI/hardware/create.py | 8 ++++++++ SoftLayer/CLI/virt/create.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index a1d373e14..19d100fc4 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -41,6 +41,10 @@ def cli(env, **args): """Order/create a dedicated server.""" mgr = SoftLayer.HardwareManager(env.client) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] # Get the SSH keys ssh_keys = [] @@ -99,6 +103,10 @@ def cli(env, **args): return if do_create: + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting dedicated server order.') diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index ad8b3b35b..5953364db 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -218,8 +218,16 @@ def cli(env, **args): create_args = _parse_create_args(env.client, args) test = args.get('test', False) do_create = not (args.get('export') or test) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] if do_create: + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting virtual server order.') From 0fa4dfd9f8723ce41d42f4c999f8a71c19b11a9e Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Mar 2022 17:05:06 -0400 Subject: [PATCH 0281/1050] fix the error unit test --- tests/CLI/modules/server_tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index a150217e2..b026fa8cf 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -397,8 +397,9 @@ def test_create_server(self, order_mock): ]) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'id': 98765, 'created': '2013-08-02 15:23:47'}) + self.assertEqual( + str(result.output), + 'Warning: Closed soon: TEST00.pod2\n{\n "id": 98765,\n "created": "2013-08-02 15:23:47"\n}\n') @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): From 3de562bca57e4a7a23b3d41ed7aec45168a0a23f Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 09:11:47 -0400 Subject: [PATCH 0282/1050] fix the team code review and fix the unit test --- SoftLayer/CLI/order/place.py | 9 +++++++++ tests/CLI/modules/order_tests.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 531bacee5..e11209bdb 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -8,6 +8,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer.managers import NetworkManager from SoftLayer.managers import ordering COLUMNS = ['keyName', @@ -64,6 +65,10 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, """ manager = ordering.OrderingManager(env.client) + network = NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] if extras: try: @@ -90,6 +95,10 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, ]) else: + print(args) + for pod in pods: + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort("Aborting order.") diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index be03bbd17..a86257b50 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -141,10 +141,10 @@ def test_place(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual({'id': 1234, - 'created': order_date, - 'status': 'APPROVED'}, - json.loads(result.output)) + self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" + 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', + str(result.output)) def test_place_with_quantity(self): order_date = '2017-04-04 07:39:20' @@ -162,10 +162,10 @@ def test_place_with_quantity(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual({'id': 1234, - 'created': order_date, - 'status': 'APPROVED'}, - json.loads(result.output)) + self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" + 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', + str(result.output)) def test_place_extras_parameter_fail(self): result = self.run_command(['-y', 'order', 'place', 'package', 'DALLAS13', 'ITEM1', From 86be7809e551314acea2f5a31c92dcf29b1971b6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 16:39:07 -0400 Subject: [PATCH 0283/1050] slcli licenses is missing the help text --- SoftLayer/CLI/licenses/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py index e69de29bb..74c74f884 100644 --- a/SoftLayer/CLI/licenses/__init__.py +++ b/SoftLayer/CLI/licenses/__init__.py @@ -0,0 +1,2 @@ +"""VMware licenses.""" +# :license: MIT, see LICENSE for more details. From 77794a5e6021a78891532f0863b38f0f146564ab Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Mar 2022 16:14:40 -0500 Subject: [PATCH 0284/1050] Version to 6.0.2, locked click to 8.0.4 for now --- CHANGELOG.md | 9 +++++++++ SoftLayer/consts.py | 2 +- setup.py | 4 ++-- tools/requirements.txt | 2 +- tools/test-requirements.txt | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6f55a71..1749efaea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log +## [6.0.2] - 2022-03-30 + +## What's Changed +* New Command slcli hardware|virtual monitoring by @caberos in https://github.com/softlayer/softlayer-python/pull/1593 +* When listing datacenters/pods, mark those that are closing soon. by @caberos in https://github.com/softlayer/softlayer-python/pull/1597 + + +**Full Changelog**: https://github.com/softlayer/softlayer-python/compare/v6.0.1...v6.0.2 + ## [6.0.1] - 2022-03-11 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 54e4dfd22..32ebd4ec4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v6.0.1' +VERSION = 'v6.0.2' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 0138ba9de..09921feaf 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='6.0.1', + version='6.0.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, long_description_content_type='text/x-rst', @@ -35,7 +35,7 @@ python_requires='>=3.5', install_requires=[ 'prettytable >= 2.0.0', - 'click >= 7', + 'click == 8.0.4', 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', diff --git a/tools/requirements.txt b/tools/requirements.txt index 09f985d84..dd85ec17e 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,5 +1,5 @@ prettytable >= 2.0.0 -click >= 7 +click == 8.0.4 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 3cc0f32e6..ac58d1241 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -5,7 +5,7 @@ pytest-cov mock sphinx prettytable >= 2.0.0 -click >= 7 +click == 8.0.4 requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 From 39f82b1136eede3f2e038731f6deb2721ca73fe3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Mar 2022 19:01:59 -0400 Subject: [PATCH 0285/1050] fix the team code review and unit test --- SoftLayer/CLI/order/place.py | 1 - tests/CLI/modules/order_tests.py | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index e11209bdb..2b20be224 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -95,7 +95,6 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, ]) else: - print(args) for pod in pods: closure.append(pod['name']) click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a86257b50..5eec6c18b 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -141,8 +141,7 @@ def test_place(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" - 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + self.assertEqual('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', str(result.output)) @@ -162,8 +161,7 @@ def test_place_with_quantity(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') - self.assertEqual("('package', 'DALLAS13', ('ITEM1',))\n" - 'Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' + self.assertEqual('Warning: Closed soon: ams01.pod01, wdc07.pod01, TEST00.pod2\n' '{\n "id": 1234,\n "created": "2017-04-04 07:39:20",\n "status": "APPROVED"\n}\n', str(result.output)) From d045f0fced445ba1b8b892075981e3d55fedeb52 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Mon, 4 Apr 2022 10:03:27 -0400 Subject: [PATCH 0286/1050] updated number of updates in the command account event-detail --- SoftLayer/CLI/account/event_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index 2c1ee80c2..386a41710 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -65,9 +65,9 @@ def update_table(event): """Formats a basic event update table""" update_number = 0 for update in event.get('updates', []): + update_number = update_number + 1 header = "======= Update #%s on %s =======" % (update_number, utils.clean_time(update.get('startDate'))) click.secho(header, fg='green') - update_number = update_number + 1 text = update.get('contents') # deals with all the \r\n from the API click.secho(utils.clean_splitlines(text)) From 6d5374e3fdfbfb72c2b090fc79006ef81f2fe45e Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 5 Apr 2022 15:16:16 -0400 Subject: [PATCH 0287/1050] added options in command -slcli account events- for show just one o two specific tables --- SoftLayer/CLI/account/events.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 7d4803b42..43d85b537 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -11,8 +11,14 @@ @click.command() @click.option('--ack-all', is_flag=True, default=False, help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal") +@click.option('--planned', is_flag=True, default=False, + help="Show just planned events") +@click.option('--unplanned', is_flag=True, default=False, + help="Show just unplanned events") +@click.option('--announcement', is_flag=True, default=False, + help="Show just announcement events") @environment.pass_env -def cli(env, ack_all): +def cli(env, ack_all, planned, unplanned, announcement): """Summary and acknowledgement of upcoming and ongoing maintenance events""" manager = AccountManager(env.client) @@ -21,13 +27,22 @@ def cli(env, ack_all): announcement_events = manager.get_upcoming_events("ANNOUNCEMENT") add_ack_flag(planned_events, manager, ack_all) - env.fout(planned_event_table(planned_events)) - add_ack_flag(unplanned_events, manager, ack_all) - env.fout(unplanned_event_table(unplanned_events)) - add_ack_flag(announcement_events, manager, ack_all) - env.fout(announcement_event_table(announcement_events)) + + if planned: + env.fout(planned_event_table(planned_events)) + + if unplanned: + env.fout(unplanned_event_table(unplanned_events)) + + if announcement: + env.fout(announcement_event_table(announcement_events)) + + if not planned and not unplanned and not announcement: + env.fout(planned_event_table(planned_events)) + env.fout(unplanned_event_table(unplanned_events)) + env.fout(announcement_event_table(announcement_events)) def add_ack_flag(events, manager, ack_all): From 62ef9abe68599afc5e98a26eb46360e00760e525 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 5 Apr 2022 17:25:43 -0500 Subject: [PATCH 0288/1050] #1602 groundwork for adding a SOAP style client --- SoftLayer/transports.py | 589 --------------------- SoftLayer/transports/__init__.py | 45 ++ SoftLayer/transports/debug.py | 61 +++ SoftLayer/transports/fixture.py | 30 ++ SoftLayer/transports/rest.py | 182 +++++++ SoftLayer/transports/soap.py | 83 +++ SoftLayer/transports/timing.py | 40 ++ SoftLayer/transports/transport.py | 154 ++++++ SoftLayer/transports/xmlrpc.py | 171 ++++++ setup.py | 3 +- tests/transport_tests.py | 791 ---------------------------- tests/transports/__init__.py | 0 tests/transports/debug_tests.py | 84 +++ tests/transports/rest_tests.py | 365 +++++++++++++ tests/transports/soap_tests.py | 59 +++ tests/transports/transport_tests.py | 73 +++ tests/transports/xmlrpc_tests.py | 467 ++++++++++++++++ tools/requirements.txt | 1 + tools/test-requirements.txt | 1 + 19 files changed, 1818 insertions(+), 1381 deletions(-) delete mode 100644 SoftLayer/transports.py create mode 100644 SoftLayer/transports/__init__.py create mode 100644 SoftLayer/transports/debug.py create mode 100644 SoftLayer/transports/fixture.py create mode 100644 SoftLayer/transports/rest.py create mode 100644 SoftLayer/transports/soap.py create mode 100644 SoftLayer/transports/timing.py create mode 100644 SoftLayer/transports/transport.py create mode 100644 SoftLayer/transports/xmlrpc.py create mode 100644 tests/transports/__init__.py create mode 100644 tests/transports/debug_tests.py create mode 100644 tests/transports/rest_tests.py create mode 100644 tests/transports/soap_tests.py create mode 100644 tests/transports/transport_tests.py create mode 100644 tests/transports/xmlrpc_tests.py diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py deleted file mode 100644 index e243f16f6..000000000 --- a/SoftLayer/transports.py +++ /dev/null @@ -1,589 +0,0 @@ -""" - SoftLayer.transports - ~~~~~~~~~~~~~~~~~~~~ - XML-RPC transport layer that uses the requests library. - - :license: MIT, see LICENSE for more details. -""" -import base64 -import importlib -import json -import logging -import re -from string import Template -import time -import xmlrpc.client - -import requests -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry - -from SoftLayer import consts -from SoftLayer import exceptions -from SoftLayer import utils - -LOGGER = logging.getLogger(__name__) -# transports.Request does have a lot of instance attributes. :( -# pylint: disable=too-many-instance-attributes, no-self-use - -__all__ = [ - 'Request', - 'XmlRpcTransport', - 'RestTransport', - 'TimingTransport', - 'DebugTransport', - 'FixtureTransport', - 'SoftLayerListResult', -] - -REST_SPECIAL_METHODS = { - # 'deleteObject': 'DELETE', - 'createObject': 'POST', - 'createObjects': 'POST', - 'editObject': 'PUT', - 'editObjects': 'PUT', -} - - -def get_session(user_agent): - """Sets up urllib sessions""" - - client = requests.Session() - client.headers.update({ - 'Content-Type': 'application/json', - 'User-Agent': user_agent, - }) - retry = Retry(connect=3, backoff_factor=3) - adapter = HTTPAdapter(max_retries=retry) - client.mount('https://', adapter) - return client - - -class Request(object): - """Transport request object.""" - - def __init__(self): - #: API service name. E.G. SoftLayer_Account - self.service = None - - #: API method name. E.G. getObject - self.method = None - - #: API Parameters. - self.args = tuple() - - #: API headers, used for authentication, masks, limits, offsets, etc. - self.headers = {} - - #: Transport user. - self.transport_user = None - - #: Transport password. - self.transport_password = None - - #: Transport headers. - self.transport_headers = {} - - #: Boolean specifying if the server certificate should be verified. - self.verify = None - - #: Client certificate file path. - self.cert = None - - #: InitParameter/identifier of an object. - self.identifier = None - - #: SoftLayer mask (dict or string). - self.mask = None - - #: SoftLayer Filter (dict). - self.filter = None - - #: Integer result limit. - self.limit = None - - #: Integer result offset. - self.offset = None - - #: Integer call start time - self.start_time = None - - #: Integer call end time - self.end_time = None - - #: String full url - self.url = None - - #: String result of api call - self.result = None - - #: String payload to send in - self.payload = None - - #: Exception any exceptions that got caught - self.exception = None - - def __repr__(self): - """Prints out what this call is all about""" - pretty_mask = utils.clean_string(self.mask) - pretty_filter = self.filter - param_string = "id={id}, mask='{mask}', filter='{filter}', args={args}, limit={limit}, offset={offset}".format( - id=self.identifier, mask=pretty_mask, filter=pretty_filter, - args=self.args, limit=self.limit, offset=self.offset) - return "{service}::{method}({params})".format( - service=self.service, method=self.method, params=param_string) - - -class SoftLayerListResult(list): - """A SoftLayer API list result.""" - - def __init__(self, items=None, total_count=0): - - #: total count of items that exist on the server. This is useful when - #: paginating through a large list of objects. - self.total_count = total_count - super().__init__(items) - - -class XmlRpcTransport(object): - """XML-RPC transport.""" - - def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): - - self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') - self.timeout = timeout or None - self.proxy = proxy - self.user_agent = user_agent or consts.USER_AGENT - self.verify = verify - self._client = None - - @property - def client(self): - """Returns client session object""" - - if self._client is None: - self._client = get_session(self.user_agent) - return self._client - - def __call__(self, request): - """Makes a SoftLayer API call against the XML-RPC endpoint. - - :param request request: Request object - """ - largs = list(request.args) - headers = request.headers - - auth = None - if request.transport_user: - auth = requests.auth.HTTPBasicAuth(request.transport_user, request.transport_password) - - if request.identifier is not None: - header_name = request.service + 'InitParameters' - headers[header_name] = {'id': request.identifier} - - if request.mask is not None: - if isinstance(request.mask, dict): - mheader = '%sObjectMask' % request.service - else: - mheader = 'SoftLayer_ObjectMask' - request.mask = _format_object_mask(request.mask) - headers.update({mheader: {'mask': request.mask}}) - - if request.filter is not None: - headers['%sObjectFilter' % request.service] = request.filter - - if request.limit: - headers['resultLimit'] = { - 'limit': request.limit, - 'offset': request.offset or 0, - } - - largs.insert(0, {'headers': headers}) - request.transport_headers.setdefault('Content-Type', 'application/xml') - request.transport_headers.setdefault('User-Agent', self.user_agent) - - request.url = '/'.join([self.endpoint_url, request.service]) - request.payload = xmlrpc.client.dumps(tuple(largs), - methodname=request.method, - allow_none=True, - encoding="iso-8859-1") - - # Prefer the request setting, if it's not None - verify = request.verify - if verify is None: - request.verify = self.verify - - try: - resp = self.client.request('POST', request.url, - data=request.payload.encode(), - auth=auth, - headers=request.transport_headers, - timeout=self.timeout, - verify=request.verify, - cert=request.cert, - proxies=_proxies_dict(self.proxy)) - - resp.raise_for_status() - result = xmlrpc.client.loads(resp.content)[0][0] - if isinstance(result, list): - return SoftLayerListResult( - result, int(resp.headers.get('softlayer-total-items', 0))) - else: - return result - except xmlrpc.client.Fault as ex: - # These exceptions are formed from the XML-RPC spec - # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php - error_mapping = { - '-32700': exceptions.NotWellFormed, - '-32701': exceptions.UnsupportedEncoding, - '-32702': exceptions.InvalidCharacter, - '-32600': exceptions.SpecViolation, - '-32601': exceptions.MethodNotFound, - '-32602': exceptions.InvalidMethodParameters, - '-32603': exceptions.InternalError, - '-32500': exceptions.ApplicationError, - '-32400': exceptions.RemoteSystemError, - '-32300': exceptions.TransportError, - } - _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) - raise _ex(ex.faultCode, ex.faultString) from ex - except requests.HTTPError as ex: - raise exceptions.TransportError(ex.response.status_code, str(ex)) - except requests.RequestException as ex: - raise exceptions.TransportError(0, str(ex)) - - def print_reproduceable(self, request): - """Prints out the minimal python code to reproduce a specific request - - The will also automatically replace the API key so its not accidently exposed. - - :param request request: Request object - """ - output = Template('''============= testing.py ============= -import requests -from requests.auth import HTTPBasicAuth -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -from xml.etree import ElementTree -client = requests.Session() -client.headers.update({'Content-Type': 'application/json', 'User-Agent': 'softlayer-python/testing',}) -retry = Retry(connect=3, backoff_factor=3) -adapter = HTTPAdapter(max_retries=retry) -client.mount('https://', adapter) -# This is only needed if you are using an cloud.ibm.com api key -#auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) -auth=None -url = '$url' -payload = $payload -transport_headers = $transport_headers -timeout = $timeout -verify = $verify -cert = $cert -proxy = $proxy -response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout, - verify=verify, cert=cert, proxies=proxy, auth=auth) -xml = ElementTree.fromstring(response.content) -ElementTree.dump(xml) -==========================''') - - safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) - safe_payload = re.sub(r'(\s+)', r' ', safe_payload) - safe_payload = safe_payload.encode() - substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, - timeout=self.timeout, verify=request.verify, cert=request.cert, - proxy=_proxies_dict(self.proxy)) - return output.substitute(substitutions) - - -class RestTransport(object): - """REST transport. - - REST calls should mostly work, but is not fully tested. - XML-RPC should be used when in doubt - """ - - def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): - - self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT_REST).rstrip('/') - self.timeout = timeout or None - self.proxy = proxy - self.user_agent = user_agent or consts.USER_AGENT - self.verify = verify - self._client = None - - @property - def client(self): - """Returns client session object""" - - if self._client is None: - self._client = get_session(self.user_agent) - return self._client - - def __call__(self, request): - """Makes a SoftLayer API call against the REST endpoint. - - REST calls should mostly work, but is not fully tested. - XML-RPC should be used when in doubt - - :param request request: Request object - """ - params = request.headers.copy() - if request.mask: - request.mask = _format_object_mask(request.mask) - params['objectMask'] = request.mask - - if request.limit or request.offset: - limit = request.limit or 0 - offset = request.offset or 0 - params['resultLimit'] = "%d,%d" % (offset, limit) - - if request.filter: - params['objectFilter'] = json.dumps(request.filter) - - request.params = params - - auth = None - if request.transport_user: - auth = requests.auth.HTTPBasicAuth( - request.transport_user, - request.transport_password, - ) - - method = REST_SPECIAL_METHODS.get(request.method) - - if method is None: - method = 'GET' - - body = {} - if request.args: - # NOTE(kmcdonald): force POST when there are arguments because - # the request body is ignored otherwise. - method = 'POST' - body['parameters'] = request.args - - if body: - request.payload = json.dumps(body, cls=ComplexEncoder) - - url_parts = [self.endpoint_url, request.service] - if request.identifier is not None: - url_parts.append(str(request.identifier)) - - if request.method is not None: - url_parts.append(request.method) - - request.url = '%s.%s' % ('/'.join(url_parts), 'json') - - # Prefer the request setting, if it's not None - - if request.verify is None: - request.verify = self.verify - - try: - resp = self.client.request(method, request.url, - auth=auth, - headers=request.transport_headers, - params=request.params, - data=request.payload, - timeout=self.timeout, - verify=request.verify, - cert=request.cert, - proxies=_proxies_dict(self.proxy)) - - request.url = resp.url - - resp.raise_for_status() - - if resp.text != "": - try: - result = json.loads(resp.text) - except ValueError as json_ex: - LOGGER.warning(json_ex) - raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) - else: - raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - - request.result = result - - if isinstance(result, list): - return SoftLayerListResult( - result, int(resp.headers.get('softlayer-total-items', 0))) - else: - return result - except requests.HTTPError as ex: - try: - message = json.loads(ex.response.text)['error'] - request.url = ex.response.url - except ValueError as json_ex: - if ex.response.text == "": - raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - LOGGER.warning(json_ex) - raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) - - raise exceptions.SoftLayerAPIError(ex.response.status_code, message) - except requests.RequestException as ex: - raise exceptions.TransportError(0, str(ex)) - - def print_reproduceable(self, request): - """Prints out the minimal python code to reproduce a specific request - - The will also automatically replace the API key so its not accidently exposed. - - :param request request: Request object - """ - command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'" - - method = REST_SPECIAL_METHODS.get(request.method) - - if method is None: - method = 'GET' - if request.args: - method = 'POST' - - data = '' - if request.payload is not None: - data = "-d '{}'".format(request.payload) - - headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] - headers = " -H ".join(headers) - return command.format(method=method, headers=headers, data=data, uri=request.url) - - -class DebugTransport(object): - """Transport that records API call timings.""" - - def __init__(self, transport): - self.transport = transport - - #: List All API calls made during a session - self.requests = [] - - def __call__(self, call): - call.start_time = time.time() - - self.pre_transport_log(call) - try: - call.result = self.transport(call) - except (exceptions.SoftLayerAPIError, exceptions.TransportError) as ex: - call.exception = ex - - self.post_transport_log(call) - - call.end_time = time.time() - self.requests.append(call) - - if call.exception is not None: - LOGGER.debug(self.print_reproduceable(call)) - raise call.exception - - return call.result - - def pre_transport_log(self, call): - """Prints a warning before calling the API """ - output = "Calling: {})".format(call) - LOGGER.warning(output) - - def post_transport_log(self, call): - """Prints the result "Returned Data: \n%s" % (call.result)of an API call""" - output = "Returned Data: \n{}".format(call.result) - LOGGER.debug(output) - - def get_last_calls(self): - """Returns all API calls for a session""" - return self.requests - - def print_reproduceable(self, call): - """Prints a reproduceable debugging output""" - return self.transport.print_reproduceable(call) - - -class TimingTransport(object): - """Transport that records API call timings.""" - - def __init__(self, transport): - self.transport = transport - self.last_calls = [] - - def __call__(self, call): - """See Client.call for documentation.""" - start_time = time.time() - - result = self.transport(call) - - end_time = time.time() - self.last_calls.append((call, start_time, end_time - start_time)) - return result - - def get_last_calls(self): - """Retrieves the last_calls property. - - This property will contain a list of tuples in the form - (Request, initiated_utc_timestamp, execution_time) - """ - last_calls = self.last_calls - self.last_calls = [] - return last_calls - - def print_reproduceable(self, call): - """Not Implemented""" - return call.service - - -class FixtureTransport(object): - """Implements a transport which returns fixtures.""" - - def __call__(self, call): - """Load fixture from the default fixture path.""" - try: - module_path = 'SoftLayer.fixtures.%s' % call.service - module = importlib.import_module(module_path) - except ImportError as ex: - message = '{} fixture is not implemented'.format(call.service) - raise NotImplementedError(message) from ex - try: - return getattr(module, call.method) - except AttributeError as ex: - message = '{}::{} fixture is not implemented'.format(call.service, call.method) - raise NotImplementedError(message) from ex - - def print_reproduceable(self, call): - """Not Implemented""" - return call.service - - -def _proxies_dict(proxy): - """Makes a proxy dict appropriate to pass to requests.""" - if not proxy: - return None - return {'http': proxy, 'https': proxy} - - -def _format_object_mask(objectmask): - """Format the new style object mask. - - This wraps the user mask with mask[USER_MASK] if it does not already - have one. This makes it slightly easier for users. - - :param objectmask: a string-based object mask - - """ - objectmask = objectmask.strip() - - if (not objectmask.startswith('mask') and - not objectmask.startswith('[') and - not objectmask.startswith('filteredMask')): - objectmask = "mask[%s]" % objectmask - return objectmask - - -class ComplexEncoder(json.JSONEncoder): - """ComplexEncoder helps jsonencoder deal with byte strings""" - - def default(self, o): - """Encodes o as JSON""" - - # Base64 encode bytes type objects. - if isinstance(o, bytes): - base64_bytes = base64.b64encode(o) - return base64_bytes.decode("utf-8") - # Let the base class default method raise the TypeError - return json.JSONEncoder.default(self, o) diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py new file mode 100644 index 000000000..bbecea227 --- /dev/null +++ b/SoftLayer/transports/__init__.py @@ -0,0 +1,45 @@ +""" + SoftLayer.transports + ~~~~~~~~~~~~~~~~~~~~ + XML-RPC transport layer that uses the requests library. + + :license: MIT, see LICENSE for more details. +""" + + +import requests + + +# Required imports to not break existing code. +from .rest import RestTransport +from .xmlrpc import XmlRpcTransport +from .fixture import FixtureTransport +from .timing import TimingTransport +from .debug import DebugTransport + +from .transport import Request +from .transport import SoftLayerListResult as SoftLayerListResult + + +# transports.Request does have a lot of instance attributes. :( +# pylint: disable=too-many-instance-attributes, no-self-use + +__all__ = [ + 'Request', + 'XmlRpcTransport', + 'RestTransport', + 'TimingTransport', + 'DebugTransport', + 'FixtureTransport', + 'SoftLayerListResult' +] + + + + + + + + + + diff --git a/SoftLayer/transports/debug.py b/SoftLayer/transports/debug.py new file mode 100644 index 000000000..31b93b847 --- /dev/null +++ b/SoftLayer/transports/debug.py @@ -0,0 +1,61 @@ +""" + SoftLayer.transports.debug + ~~~~~~~~~~~~~~~~~~~~ + Debugging transport. Will print out verbose logging information. + + :license: MIT, see LICENSE for more details. +""" + +import logging +import time + +from SoftLayer import exceptions + + +class DebugTransport(object): + """Transport that records API call timings.""" + + def __init__(self, transport): + self.transport = transport + + #: List All API calls made during a session + self.requests = [] + self.logger = logging.getLogger(__name__) + + def __call__(self, call): + call.start_time = time.time() + + self.pre_transport_log(call) + try: + call.result = self.transport(call) + except (exceptions.SoftLayerAPIError, exceptions.TransportError) as ex: + call.exception = ex + + self.post_transport_log(call) + + call.end_time = time.time() + self.requests.append(call) + + if call.exception is not None: + self.logger.debug(self.print_reproduceable(call)) + raise call.exception + + return call.result + + def pre_transport_log(self, call): + """Prints a warning before calling the API """ + output = "Calling: {})".format(call) + self.logger.warning(output) + + def post_transport_log(self, call): + """Prints the result "Returned Data: \n%s" % (call.result)of an API call""" + output = "Returned Data: \n{}".format(call.result) + self.logger.debug(output) + + def get_last_calls(self): + """Returns all API calls for a session""" + return self.requests + + def print_reproduceable(self, call): + """Prints a reproduceable debugging output""" + return self.transport.print_reproduceable(call) \ No newline at end of file diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py new file mode 100644 index 000000000..3eece28fc --- /dev/null +++ b/SoftLayer/transports/fixture.py @@ -0,0 +1,30 @@ +""" + SoftLayer.transports.fixture + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Fixture transport, used for unit tests + + :license: MIT, see LICENSE for more details. +""" + +import importlib + +class FixtureTransport(object): + """Implements a transport which returns fixtures.""" + + def __call__(self, call): + """Load fixture from the default fixture path.""" + try: + module_path = 'SoftLayer.fixtures.%s' % call.service + module = importlib.import_module(module_path) + except ImportError as ex: + message = '{} fixture is not implemented'.format(call.service) + raise NotImplementedError(message) from ex + try: + return getattr(module, call.method) + except AttributeError as ex: + message = '{}::{} fixture is not implemented'.format(call.service, call.method) + raise NotImplementedError(message) from ex + + def print_reproduceable(self, call): + """Not Implemented""" + return call.service \ No newline at end of file diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py new file mode 100644 index 000000000..e80d5bb35 --- /dev/null +++ b/SoftLayer/transports/rest.py @@ -0,0 +1,182 @@ +""" + SoftLayer.transports.rest + ~~~~~~~~~~~~~~~~~~~~ + REST Style transport library + + :license: MIT, see LICENSE for more details. +""" + +import json +import logging +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +REST_SPECIAL_METHODS = { + # 'deleteObject': 'DELETE', + 'createObject': 'POST', + 'createObjects': 'POST', + 'editObject': 'PUT', + 'editObjects': 'PUT', +} + + +class RestTransport(object): + """REST transport. + + REST calls should mostly work, but is not fully tested. + XML-RPC should be used when in doubt + """ + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT_REST).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + self.logger = logging.getLogger(__name__) + + @property + def client(self): + """Returns client session object""" + + if self._client is None: + self._client = get_session(self.user_agent) + return self._client + + def __call__(self, request): + """Makes a SoftLayer API call against the REST endpoint. + + REST calls should mostly work, but is not fully tested. + XML-RPC should be used when in doubt + + :param request request: Request object + """ + params = request.headers.copy() + if request.mask: + request.mask = _format_object_mask(request.mask) + params['objectMask'] = request.mask + + if request.limit or request.offset: + limit = request.limit or 0 + offset = request.offset or 0 + params['resultLimit'] = "%d,%d" % (offset, limit) + + if request.filter: + params['objectFilter'] = json.dumps(request.filter) + + request.params = params + + auth = None + if request.transport_user: + auth = requests.auth.HTTPBasicAuth( + request.transport_user, + request.transport_password, + ) + + method = REST_SPECIAL_METHODS.get(request.method) + + if method is None: + method = 'GET' + + body = {} + if request.args: + # NOTE(kmcdonald): force POST when there are arguments because + # the request body is ignored otherwise. + method = 'POST' + body['parameters'] = request.args + + if body: + request.payload = json.dumps(body, cls=ComplexEncoder) + + url_parts = [self.endpoint_url, request.service] + if request.identifier is not None: + url_parts.append(str(request.identifier)) + + if request.method is not None: + url_parts.append(request.method) + + request.url = '%s.%s' % ('/'.join(url_parts), 'json') + + # Prefer the request setting, if it's not None + + if request.verify is None: + request.verify = self.verify + + try: + resp = self.client.request(method, request.url, + auth=auth, + headers=request.transport_headers, + params=request.params, + data=request.payload, + timeout=self.timeout, + verify=request.verify, + cert=request.cert, + proxies=_proxies_dict(self.proxy)) + + request.url = resp.url + + resp.raise_for_status() + + if resp.text != "": + try: + result = json.loads(resp.text) + except ValueError as json_ex: + self.logger.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) + else: + raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") + + request.result = result + + if isinstance(result, list): + return SoftLayerListResult( + result, int(resp.headers.get('softlayer-total-items', 0))) + else: + return result + except requests.HTTPError as ex: + try: + message = json.loads(ex.response.text)['error'] + request.url = ex.response.url + except ValueError as json_ex: + if ex.response.text == "": + raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") + self.logger.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) + + raise exceptions.SoftLayerAPIError(ex.response.status_code, message) + except requests.RequestException as ex: + raise exceptions.TransportError(0, str(ex)) + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + command = "curl -u $SL_USER:$SL_APIKEY -X {method} -H {headers} {data} '{uri}'" + + method = REST_SPECIAL_METHODS.get(request.method) + + if method is None: + method = 'GET' + if request.args: + method = 'POST' + + data = '' + if request.payload is not None: + data = "-d '{}'".format(request.payload) + + headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] + headers = " -H ".join(headers) + return command.format(method=method, headers=headers, data=data, uri=request.url) \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py new file mode 100644 index 000000000..b89fb090c --- /dev/null +++ b/SoftLayer/transports/soap.py @@ -0,0 +1,83 @@ +""" + SoftLayer.transports.soap + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + SOAP Style transport library + + :license: MIT, see LICENSE for more details. +""" +import logging +import re +from string import Template +from zeep import Client, Settings, Transport, xsd +from zeep.helpers import serialize_object +from zeep.cache import SqliteCache + +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +from pprint import pprint as pp +class SoapTransport(object): + """XML-RPC transport.""" + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + # Throw an error for py < 3.6 because of f-strings + logging.getLogger('zeep').setLevel(logging.ERROR) + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + + def __call__(self, request): + """Makes a SoftLayer API call against the SOAP endpoint. + + :param request request: Request object + """ + print("Making a SOAP API CALL...") + # client = Client(f"{self.endpoint_url}/{request.service}?wsdl") + zeep_settings = Settings(strict=False, xml_huge_tree=True) + zeep_transport = Transport(cache=SqliteCache(timeout=86400)) + client = Client(f"{self.endpoint_url}/{request.service}?wsdl", + settings=zeep_settings, transport=zeep_transport) + # authXsd = xsd.Element( + # f"{self.endpoint_url}/authenticate", + # xsd.ComplexType([ + # xsd.Element(f"{self.endpoint_url}/username", xsd.String()), + # xsd.Element(f"{self.endpoint_url}/apiKey", xsd.String()) + # ]) + # ) + xsdUserAuth = xsd.Element( + '{http://api.softlayer.com/soap/v3.1/}authenticate', + xsd.ComplexType([ + xsd.Element('{http://api.softlayer.com/soap/v3.1/}username', xsd.String()), + xsd.Element('{http://api.softlayer.com/soap/v3.1/}apiKey', xsd.String()) + ]) + ) + # transport = Transport(session=get_session()) + + authHeader = xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + method = getattr(client.service, request.method) + result = client.service.getObject(_soapheaders=[authHeader]) + return serialize_object(result) + # result = transport.post(f"{self.endpoint_url}/{request.service}") + + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + + return "THE SOAP API CALL..." diff --git a/SoftLayer/transports/timing.py b/SoftLayer/transports/timing.py new file mode 100644 index 000000000..5b9345276 --- /dev/null +++ b/SoftLayer/transports/timing.py @@ -0,0 +1,40 @@ +""" + SoftLayer.transports.timing + ~~~~~~~~~~~~~~~~~~~~ + Timing transport, used when you want to know how long an API call took. + + :license: MIT, see LICENSE for more details. +""" +import time + + +class TimingTransport(object): + """Transport that records API call timings.""" + + def __init__(self, transport): + self.transport = transport + self.last_calls = [] + + def __call__(self, call): + """See Client.call for documentation.""" + start_time = time.time() + + result = self.transport(call) + + end_time = time.time() + self.last_calls.append((call, start_time, end_time - start_time)) + return result + + def get_last_calls(self): + """Retrieves the last_calls property. + + This property will contain a list of tuples in the form + (Request, initiated_utc_timestamp, execution_time) + """ + last_calls = self.last_calls + self.last_calls = [] + return last_calls + + def print_reproduceable(self, call): + """Not Implemented""" + return call.service diff --git a/SoftLayer/transports/transport.py b/SoftLayer/transports/transport.py new file mode 100644 index 000000000..40a8e872b --- /dev/null +++ b/SoftLayer/transports/transport.py @@ -0,0 +1,154 @@ +""" + SoftLayer.transports.transport + ~~~~~~~~~~~~~~~~~~~~ + Common functions for transporting API requests + + :license: MIT, see LICENSE for more details. +""" +import base64 +import json +import requests +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + + +from SoftLayer import utils + + +def get_session(user_agent): + """Sets up urllib sessions""" + + client = requests.Session() + client.headers.update({ + 'Content-Type': 'application/json', + 'User-Agent': user_agent, + }) + retry = Retry(connect=3, backoff_factor=3) + adapter = HTTPAdapter(max_retries=retry) + client.mount('https://', adapter) + return client + + +# transports.Request does have a lot of instance attributes. :( +# pylint: disable=too-many-instance-attributes, no-self-use +class Request(object): + """Transport request object.""" + + def __init__(self): + #: API service name. E.G. SoftLayer_Account + self.service = None + + #: API method name. E.G. getObject + self.method = None + + #: API Parameters. + self.args = tuple() + + #: API headers, used for authentication, masks, limits, offsets, etc. + self.headers = {} + + #: Transport user. + self.transport_user = None + + #: Transport password. + self.transport_password = None + + #: Transport headers. + self.transport_headers = {} + + #: Boolean specifying if the server certificate should be verified. + self.verify = None + + #: Client certificate file path. + self.cert = None + + #: InitParameter/identifier of an object. + self.identifier = None + + #: SoftLayer mask (dict or string). + self.mask = None + + #: SoftLayer Filter (dict). + self.filter = None + + #: Integer result limit. + self.limit = None + + #: Integer result offset. + self.offset = None + + #: Integer call start time + self.start_time = None + + #: Integer call end time + self.end_time = None + + #: String full url + self.url = None + + #: String result of api call + self.result = None + + #: String payload to send in + self.payload = None + + #: Exception any exceptions that got caught + self.exception = None + + def __repr__(self): + """Prints out what this call is all about""" + pretty_mask = utils.clean_string(self.mask) + pretty_filter = self.filter + param_string = "id={id}, mask='{mask}', filter='{filter}', args={args}, limit={limit}, offset={offset}".format( + id=self.identifier, mask=pretty_mask, filter=pretty_filter, + args=self.args, limit=self.limit, offset=self.offset) + return "{service}::{method}({params})".format( + service=self.service, method=self.method, params=param_string) + +class SoftLayerListResult(list): + """A SoftLayer API list result.""" + + def __init__(self, items=None, total_count=0): + + #: total count of items that exist on the server. This is useful when + #: paginating through a large list of objects. + self.total_count = total_count + super().__init__(items) + +def _proxies_dict(proxy): + """Makes a proxy dict appropriate to pass to requests.""" + if not proxy: + return None + return {'http': proxy, 'https': proxy} + + +def _format_object_mask(objectmask): + """Format the new style object mask. + + This wraps the user mask with mask[USER_MASK] if it does not already + have one. This makes it slightly easier for users. + + :param objectmask: a string-based object mask + + """ + objectmask = objectmask.strip() + + if (not objectmask.startswith('mask') and + not objectmask.startswith('[') and + not objectmask.startswith('filteredMask')): + objectmask = "mask[%s]" % objectmask + return objectmask + + +class ComplexEncoder(json.JSONEncoder): + """ComplexEncoder helps jsonencoder deal with byte strings""" + + def default(self, o): + """Encodes o as JSON""" + + # Base64 encode bytes type objects. + if isinstance(o, bytes): + base64_bytes = base64.b64encode(o) + return base64_bytes.decode("utf-8") + # Let the base class default method raise the TypeError + return json.JSONEncoder.default(self, o) diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py new file mode 100644 index 000000000..31afaf868 --- /dev/null +++ b/SoftLayer/transports/xmlrpc.py @@ -0,0 +1,171 @@ +""" + SoftLayer.transports.xmlrpc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + XML-RPC Style transport library + + :license: MIT, see LICENSE for more details. +""" +import logging +import re +from string import Template +import xmlrpc.client + +import requests + +from SoftLayer import consts +from SoftLayer import exceptions + +from .transport import _format_object_mask +from .transport import _proxies_dict +from .transport import ComplexEncoder +from .transport import get_session +from .transport import SoftLayerListResult + +class XmlRpcTransport(object): + """XML-RPC transport.""" + + def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): + + self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') + self.timeout = timeout or None + self.proxy = proxy + self.user_agent = user_agent or consts.USER_AGENT + self.verify = verify + self._client = None + + @property + def client(self): + """Returns client session object""" + + if self._client is None: + self._client = get_session(self.user_agent) + return self._client + + def __call__(self, request): + """Makes a SoftLayer API call against the XML-RPC endpoint. + + :param request request: Request object + """ + largs = list(request.args) + headers = request.headers + + auth = None + if request.transport_user: + auth = requests.auth.HTTPBasicAuth(request.transport_user, request.transport_password) + + if request.identifier is not None: + header_name = request.service + 'InitParameters' + headers[header_name] = {'id': request.identifier} + + if request.mask is not None: + if isinstance(request.mask, dict): + mheader = '%sObjectMask' % request.service + else: + mheader = 'SoftLayer_ObjectMask' + request.mask = _format_object_mask(request.mask) + headers.update({mheader: {'mask': request.mask}}) + + if request.filter is not None: + headers['%sObjectFilter' % request.service] = request.filter + + if request.limit: + headers['resultLimit'] = { + 'limit': request.limit, + 'offset': request.offset or 0, + } + + largs.insert(0, {'headers': headers}) + request.transport_headers.setdefault('Content-Type', 'application/xml') + request.transport_headers.setdefault('User-Agent', self.user_agent) + + request.url = '/'.join([self.endpoint_url, request.service]) + request.payload = xmlrpc.client.dumps(tuple(largs), + methodname=request.method, + allow_none=True, + encoding="iso-8859-1") + + # Prefer the request setting, if it's not None + verify = request.verify + if verify is None: + request.verify = self.verify + + try: + resp = self.client.request('POST', request.url, + data=request.payload.encode(), + auth=auth, + headers=request.transport_headers, + timeout=self.timeout, + verify=request.verify, + cert=request.cert, + proxies=_proxies_dict(self.proxy)) + + resp.raise_for_status() + result = xmlrpc.client.loads(resp.content)[0][0] + if isinstance(result, list): + return SoftLayerListResult( + result, int(resp.headers.get('softlayer-total-items', 0))) + else: + return result + except xmlrpc.client.Fault as ex: + # These exceptions are formed from the XML-RPC spec + # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php + error_mapping = { + '-32700': exceptions.NotWellFormed, + '-32701': exceptions.UnsupportedEncoding, + '-32702': exceptions.InvalidCharacter, + '-32600': exceptions.SpecViolation, + '-32601': exceptions.MethodNotFound, + '-32602': exceptions.InvalidMethodParameters, + '-32603': exceptions.InternalError, + '-32500': exceptions.ApplicationError, + '-32400': exceptions.RemoteSystemError, + '-32300': exceptions.TransportError, + } + _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) + raise _ex(ex.faultCode, ex.faultString) from ex + except requests.HTTPError as ex: + raise exceptions.TransportError(ex.response.status_code, str(ex)) + except requests.RequestException as ex: + raise exceptions.TransportError(0, str(ex)) + + def print_reproduceable(self, request): + """Prints out the minimal python code to reproduce a specific request + + The will also automatically replace the API key so its not accidently exposed. + + :param request request: Request object + """ + output = Template('''============= testing.py ============= +import requests +from requests.auth import HTTPBasicAuth +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry +from xml.etree import ElementTree +client = requests.Session() +client.headers.update({'Content-Type': 'application/json', 'User-Agent': 'softlayer-python/testing',}) +retry = Retry(connect=3, backoff_factor=3) +adapter = HTTPAdapter(max_retries=retry) +client.mount('https://', adapter) +# This is only needed if you are using an cloud.ibm.com api key +#auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) +auth=None +url = '$url' +payload = $payload +transport_headers = $transport_headers +timeout = $timeout +verify = $verify +cert = $cert +proxy = $proxy +response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout, + verify=verify, cert=cert, proxies=proxy, auth=auth) +xml = ElementTree.fromstring(response.content) +ElementTree.dump(xml) +==========================''') + + safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) + safe_payload = re.sub(r'(\s+)', r' ', safe_payload) + safe_payload = safe_payload.encode() + substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, + timeout=self.timeout, verify=request.verify, cert=request.cert, + proxy=_proxies_dict(self.proxy)) + return output.substitute(substitutions) diff --git a/setup.py b/setup.py index 09921feaf..97514d838 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,8 @@ 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', - 'urllib3 >= 1.24' + 'urllib3 >= 1.24', + 'zeep' ], keywords=['softlayer', 'cloud', 'slcli'], classifiers=[ diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d09a65c51..5ae0a448c 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -18,797 +18,6 @@ from SoftLayer import transports -def get_xmlrpc_response(): - response = requests.Response() - list_body = b''' - - - - - - - - -''' - response.raw = io.BytesIO(list_body) - response.headers['SoftLayer-Total-Items'] = 10 - response.status_code = 200 - return response - - -class TestXmlRpcAPICall(testing.TestCase): - - def set_up(self): - self.transport = transports.XmlRpcTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - self.response = get_xmlrpc_response() - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_call(self, request): - request.return_value = self.response - - data = ''' - -getObject - - - - -headers - - - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - resp = self.transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=None) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - def test_proxy_without_protocol(self): - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - req.proxy = 'localhost:3128' - - try: - self.assertRaises(SoftLayer.TransportError, self.transport, req) - except AssertionError: - warnings.warn("Incorrect Exception raised. Expected a " - "SoftLayer.TransportError error") - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_valid_proxy(self, request): - request.return_value = self.response - self.transport.proxy = 'http://localhost:3128' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.transport(req) - - request.assert_called_with( - 'POST', - mock.ANY, - proxies={'https': 'http://localhost:3128', - 'http': 'http://localhost:3128'}, - data=mock.ANY, - headers=mock.ANY, - timeout=None, - cert=None, - verify=True, - auth=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_identifier(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.identifier = 1234 - self.transport(req) - - _, kwargs = request.call_args - self.assertIn( - """ -id -1234 -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_filter(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - """ -operation -^= prefix -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_limit_offset(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.limit = 10 - self.transport(req) - - args, kwargs = request.call_args - self.assertIn(""" -resultLimit - -""".encode(), kwargs['data']) - self.assertIn("""limit -10 -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_old_mask(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = {"something": "nested"} - self.transport(req) - - args, kwargs = request.call_args - self.assertIn(""" -mask - - -something -nested - - -""".encode(), kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_no_mask_prefix(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "something.nested" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "mask[something.nested]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_v2(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "mask[something[nested]]" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "mask[something[nested]]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_filteredMask(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "filteredMask[something[nested]]" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn( - "filteredMask[something[nested]]".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_mask_call_v2_dot(self, request): - request.return_value = self.response - - req = transports.Request() - req.endpoint = "http://something9999999999999999999999.com" - req.service = "SoftLayer_Service" - req.method = "getObject" - req.mask = "mask.something.nested" - self.transport(req) - - args, kwargs = request.call_args - self.assertIn("mask.something.nested".encode(), - kwargs['data']) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_request_exception(self, request): - # Test Text Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.content = 'Error Code' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - self.assertRaises(SoftLayer.TransportError, self.transport, req) - - def test_print_reproduceable(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - output_text = self.transport.print_reproduceable(req) - self.assertIn("https://test.com", output_text) - - @mock.patch('SoftLayer.transports.requests.Session.request') - @mock.patch('requests.auth.HTTPBasicAuth') - def test_ibm_id_call(self, auth, request): - request.return_value = self.response - - data = ''' - -getObject - - - - -headers - - - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.transport_user = 'apikey' - req.transport_password = '1234567890qweasdzxc' - resp = self.transport(req) - - auth.assert_called_with('apikey', '1234567890qweasdzxc') - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=mock.ANY) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_call_large_number_response(self, request): - response = requests.Response() - body = b''' - - - - - - - - - bytesUsed - 2666148982056 - - - - - - - - - ''' - response.raw = io.BytesIO(body) - response.headers['SoftLayer-Total-Items'] = 1 - response.status_code = 200 - request.return_value = response - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp[0]['bytesUsed'], 2666148982056) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_nonascii_characters(self, request): - request.return_value = self.response - hostname = 'testé' - data = ''' - -getObject - - - - -headers - - - - - - - - -hostname -testé - - - - - -'''.encode() - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ({'hostname': hostname},) - req.transport_user = "testUser" - req.transport_password = "testApiKey" - resp = self.transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - headers={'Content-Type': 'application/xml', - 'User-Agent': consts.USER_AGENT}, - proxies=None, - data=data, - timeout=None, - cert=None, - verify=True, - auth=mock.ANY) - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - - -@mock.patch('SoftLayer.transports.requests.Session.request') -@pytest.mark.parametrize( - "transport_verify,request_verify,expected", - [ - (True, True, True), - (True, False, False), - (True, None, True), - - (False, True, True), - (False, False, False), - (False, None, False), - - (None, True, True), - (None, False, False), - (None, None, True), - ] -) -def test_verify(request, - transport_verify, - request_verify, - expected): - request.return_value = get_xmlrpc_response() - - transport = transports.XmlRpcTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - if request_verify is not None: - req.verify = request_verify - - if transport_verify is not None: - transport.verify = transport_verify - - transport(req) - - request.assert_called_with('POST', - 'http://something9999999999999999999999.com/SoftLayer_Service', - data=mock.ANY, - headers=mock.ANY, - cert=mock.ANY, - proxies=mock.ANY, - timeout=mock.ANY, - verify=expected, - auth=None) - - -class TestRestAPICall(testing.TestCase): - - def set_up(self): - self.transport = transports.RestTransport( - endpoint_url='http://something9999999999999999999999.com', - ) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_basic(self, request): - request().content = '[]' - request().text = '[]' - request().headers = requests.structures.CaseInsensitiveDict({ - 'SoftLayer-Total-Items': '10', - }) - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - resp = self.transport(req) - - self.assertEqual(resp, []) - self.assertIsInstance(resp, transports.SoftLayerListResult) - self.assertEqual(resp.total_count, 10) - request.assert_called_with( - 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', - headers=mock.ANY, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_http_and_json_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = ''' - "error": "description", - "code": "Error Code" - ''' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_http_and_empty_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = '' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_empty_error(self, request): - # Test empty response error. - request().text = '' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_json_error(self, request): - # Test non-json response error. - request().text = 'Not JSON' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - - def test_proxy_without_protocol(self): - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - req.proxy = 'localhost:3128' - try: - self.assertRaises(SoftLayer.TransportError, self.transport, req) - except AssertionError: - warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_valid_proxy(self, request): - request().text = '{}' - self.transport.proxy = 'http://localhost:3128' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - - self.transport(req) - - request.assert_called_with( - 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', - proxies={'https': 'http://localhost:3128', - 'http': 'http://localhost:3128'}, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - timeout=mock.ANY, - headers=mock.ANY) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_id(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=None, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_args(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ('test', 1) - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'POST', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - headers=mock.ANY, - auth=None, - data='{"parameters": ["test", 1]}', - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_args_bytes(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.args = ('test', b'asdf') - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'POST', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - headers=mock.ANY, - auth=None, - data='{"parameters": ["test", "YXNkZg=="]}', - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_filter(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectFilter': - '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_mask(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.mask = 'id,property' - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectMask': 'mask[id,property]'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - # Now test with mask[] prefix - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.mask = 'mask[id,property]' - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', - params={'objectMask': 'mask[id,property]'}, - headers=mock.ANY, - auth=None, - data=None, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_with_limit_offset(self, request): - request().text = '{}' - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - req.limit = 10 - req.offset = 5 - - resp = self.transport(req) - - self.assertEqual(resp, {}) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=None, - data=None, - params={'resultLimit': '5,10'}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_unknown_error(self, request): - e = requests.RequestException('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.content = 'Error Code' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - - self.assertRaises(SoftLayer.TransportError, self.transport, req) - - @mock.patch('SoftLayer.transports.requests.Session.request') - @mock.patch('requests.auth.HTTPBasicAuth') - def test_with_special_auth(self, auth, request): - request().text = '{}' - - user = 'asdf' - password = 'zxcv' - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'getObject' - req.identifier = 2 - req.transport_user = user - req.transport_password = password - - resp = self.transport(req) - self.assertEqual(resp, {}) - auth.assert_called_with(user, password) - request.assert_called_with( - 'GET', - 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', - headers=mock.ANY, - auth=mock.ANY, - data=None, - params={}, - verify=True, - cert=None, - proxies=None, - timeout=None) - - def test_print_reproduceable(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - output_text = self.transport.print_reproduceable(req) - self.assertIn("https://test.com", output_text) - - def test_complex_encoder_bytes(self): - to_encode = { - 'test': ['array', 0, 1, False], - 'bytes': b'ASDASDASD' - } - result = json.dumps(to_encode, cls=transports.ComplexEncoder) - # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' - # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. - self.assertIn("QVNEQVNEQVNE", result) - class TestFixtureTransport(testing.TestCase): diff --git a/tests/transports/__init__.py b/tests/transports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py new file mode 100644 index 000000000..2527cb302 --- /dev/null +++ b/tests/transports/debug_tests.py @@ -0,0 +1,84 @@ +""" + SoftLayer.tests.transports.debug + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +class TestDebugTransport(testing.TestCase): + + def set_up(self): + fixture_transport = transports.FixtureTransport() + self.transport = transports.DebugTransport(fixture_transport) + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + self.req = req + + def test_call(self): + + resp = self.transport(self.req) + self.assertEqual(resp['accountId'], 1234) + + def test_get_last_calls(self): + + resp = self.transport(self.req) + self.assertEqual(resp['accountId'], 1234) + calls = self.transport.get_last_calls() + self.assertEqual(calls[0].service, 'SoftLayer_Account') + + def test_print_reproduceable(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + output_text = self.transport.print_reproduceable(self.req) + self.assertEqual('SoftLayer_Account', output_text) + + def test_print_reproduceable_post(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + req.args = 'createObject' + + rest_transport = transports.RestTransport() + transport = transports.DebugTransport(rest_transport) + + output_text = transport.print_reproduceable(req) + + self.assertIn("https://test.com", output_text) + self.assertIn("-X POST", output_text) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = '''{ + "error": "description", + "code": "Error Code" + }''' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + rest_transport = transports.RestTransport() + transport = transports.DebugTransport(rest_transport) + self.assertRaises(SoftLayer.SoftLayerAPIError, transport, req) + calls = transport.get_last_calls() + self.assertEqual(404, calls[0].exception.faultCode) diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py new file mode 100644 index 000000000..ec634ec6c --- /dev/null +++ b/tests/transports/rest_tests.py @@ -0,0 +1,365 @@ +""" + SoftLayer.tests.transports.rest + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + +class TestRestAPICall(testing.TestCase): + + def set_up(self): + self.transport = transports.RestTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_basic(self, request): + request().content = '[]' + request().text = '[]' + request().headers = requests.structures.CaseInsensitiveDict({ + 'SoftLayer-Total-Items': '10', + }) + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + resp = self.transport(req) + + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + request.assert_called_with( + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', + headers=mock.ANY, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_http_and_json_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = ''' + "error": "description", + "code": "Error Code" + ''' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_http_and_empty_error(self, request): + # Test JSON Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.text = '' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_empty_error(self, request): + # Test empty response error. + request().text = '' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_json_error(self, request): + # Test non-json response error. + request().text = 'Not JSON' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) + + def test_proxy_without_protocol(self): + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + try: + self.assertRaises(SoftLayer.TransportError, self.transport, req) + except AssertionError: + warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_valid_proxy(self, request): + request().text = '{}' + self.transport.proxy = 'http://localhost:3128' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + + self.transport(req) + + request.assert_called_with( + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', + proxies={'https': 'http://localhost:3128', + 'http': 'http://localhost:3128'}, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + timeout=mock.ANY, + headers=mock.ANY) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_id(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=None, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_args(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ('test', 1) + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'POST', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + headers=mock.ANY, + auth=None, + data='{"parameters": ["test", 1]}', + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_args_bytes(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ('test', b'asdf') + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'POST', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + headers=mock.ANY, + auth=None, + data='{"parameters": ["test", "YXNkZg=="]}', + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_filter(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectFilter': + '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_mask(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.mask = 'id,property' + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectMask': 'mask[id,property]'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + # Now test with mask[] prefix + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.mask = 'mask[id,property]' + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', + params={'objectMask': 'mask[id,property]'}, + headers=mock.ANY, + auth=None, + data=None, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_with_limit_offset(self, request): + request().text = '{}' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + req.limit = 10 + req.offset = 5 + + resp = self.transport(req) + + self.assertEqual(resp, {}) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=None, + data=None, + params={'resultLimit': '5,10'}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_unknown_error(self, request): + e = requests.RequestException('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.content = 'Error Code' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, self.transport, req) + + @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('requests.auth.HTTPBasicAuth') + def test_with_special_auth(self, auth, request): + request().text = '{}' + + user = 'asdf' + password = 'zxcv' + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.identifier = 2 + req.transport_user = user + req.transport_password = password + + resp = self.transport(req) + self.assertEqual(resp, {}) + auth.assert_called_with(user, password) + request.assert_called_with( + 'GET', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', + headers=mock.ANY, + auth=mock.ANY, + data=None, + params={}, + verify=True, + cert=None, + proxies=None, + timeout=None) + + def test_print_reproduceable(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + output_text = self.transport.print_reproduceable(req) + self.assertIn("https://test.com", output_text) + + def test_complex_encoder_bytes(self): + to_encode = { + 'test': ['array', 0, 1, False], + 'bytes': b'ASDASDASD' + } + result = json.dumps(to_encode, cls=transports.transport.ComplexEncoder) + # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' + # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. + self.assertIn("QVNEQVNEQVNE", result) \ No newline at end of file diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py new file mode 100644 index 000000000..bfc0d4623 --- /dev/null +++ b/tests/transports/soap_tests.py @@ -0,0 +1,59 @@ +""" + SoftLayer.tests.transports.xmlrc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import os +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer.transports.soap import SoapTransport +from SoftLayer.transports import Request + + +from pprint import pprint as pp +def get_soap_response(): + response = requests.Response() + list_body = b''' + + + + + + + + +''' + response.raw = io.BytesIO(list_body) + response.headers['SoftLayer-Total-Items'] = 10 + response.status_code = 200 + return response + + +class TestXmlRpcAPICall(testing.TestCase): + + def set_up(self): + self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') + self.response = get_soap_response() + self.user = os.getenv('SL_USER') + self.password = os.environ.get('SL_APIKEY') + + def test_call(self): + request = Request() + request.service = 'SoftLayer_Account' + request.method = 'getObject' + request.transport_user = self.user + request.transport_password = self.password + data = self.transport(request) + pp(data) + self.assertEqual(data.get('id'), 307608) + diff --git a/tests/transports/transport_tests.py b/tests/transports/transport_tests.py new file mode 100644 index 000000000..32b1eaad9 --- /dev/null +++ b/tests/transports/transport_tests.py @@ -0,0 +1,73 @@ +""" + SoftLayer.tests.transports.debug + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +class TestFixtureTransport(testing.TestCase): + + def set_up(self): + self.transport = transports.FixtureTransport() + + def test_basic(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + + def test_no_module(self): + req = transports.Request() + req.service = 'Doesnt_Exist' + req.method = 'getObject' + self.assertRaises(NotImplementedError, self.transport, req) + + def test_no_method(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObjectzzzz' + self.assertRaises(NotImplementedError, self.transport, req) + + +class TestTimingTransport(testing.TestCase): + + def set_up(self): + fixture_transport = transports.FixtureTransport() + self.transport = transports.TimingTransport(fixture_transport) + + def test_call(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + + def test_get_last_calls(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp['accountId'], 1234) + calls = self.transport.get_last_calls() + self.assertEqual(calls[0][0].service, 'SoftLayer_Account') + + def test_print_reproduceable(self): + req = transports.Request() + req.service = 'SoftLayer_Account' + req.method = 'getObject' + output_text = self.transport.print_reproduceable(req) + self.assertEqual('SoftLayer_Account', output_text) diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py new file mode 100644 index 000000000..c59eded0c --- /dev/null +++ b/tests/transports/xmlrpc_tests.py @@ -0,0 +1,467 @@ +""" + SoftLayer.tests.transports.xmlrc + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import io +import json +from unittest import mock as mock +import warnings + +import pytest +import requests + +import SoftLayer +from SoftLayer import consts +from SoftLayer import testing +from SoftLayer import transports + + +def get_xmlrpc_response(): + response = requests.Response() + list_body = b''' + + + + + + + + +''' + response.raw = io.BytesIO(list_body) + response.headers['SoftLayer-Total-Items'] = 10 + response.status_code = 200 + return response + + +class TestXmlRpcAPICall(testing.TestCase): + + def set_up(self): + self.transport = transports.XmlRpcTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + self.response = get_xmlrpc_response() + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_call(self, request): + request.return_value = self.response + + data = ''' + +getObject + + + + +headers + + + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=None) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + def test_proxy_without_protocol(self): + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + req.proxy = 'localhost:3128' + + try: + self.assertRaises(SoftLayer.TransportError, self.transport, req) + except AssertionError: + warnings.warn("Incorrect Exception raised. Expected a " + "SoftLayer.TransportError error") + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_valid_proxy(self, request): + request.return_value = self.response + self.transport.proxy = 'http://localhost:3128' + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'Resource' + self.transport(req) + + request.assert_called_with( + 'POST', + mock.ANY, + proxies={'https': 'http://localhost:3128', + 'http': 'http://localhost:3128'}, + data=mock.ANY, + headers=mock.ANY, + timeout=None, + cert=None, + verify=True, + auth=None) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_identifier(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.identifier = 1234 + self.transport(req) + + _, kwargs = request.call_args + self.assertIn( + """ +id +1234 +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_filter(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + """ +operation +^= prefix +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_limit_offset(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.limit = 10 + self.transport(req) + + args, kwargs = request.call_args + self.assertIn(""" +resultLimit + +""".encode(), kwargs['data']) + self.assertIn("""limit +10 +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_old_mask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = {"something": "nested"} + self.transport(req) + + args, kwargs = request.call_args + self.assertIn(""" +mask + + +something +nested + + +""".encode(), kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_no_mask_prefix(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "something.nested" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something.nested]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_v2(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask[something[nested]]" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "mask[something[nested]]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_filteredMask(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "filteredMask[something[nested]]" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn( + "filteredMask[something[nested]]".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_mask_call_v2_dot(self, request): + request.return_value = self.response + + req = transports.Request() + req.endpoint = "http://something9999999999999999999999.com" + req.service = "SoftLayer_Service" + req.method = "getObject" + req.mask = "mask.something.nested" + self.transport(req) + + args, kwargs = request.call_args + self.assertIn("mask.something.nested".encode(), + kwargs['data']) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_request_exception(self, request): + # Test Text Error + e = requests.HTTPError('error') + e.response = mock.MagicMock() + e.response.status_code = 404 + e.response.content = 'Error Code' + request().raise_for_status.side_effect = e + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + self.assertRaises(SoftLayer.TransportError, self.transport, req) + + def test_print_reproduceable(self): + req = transports.Request() + req.url = "https://test.com" + req.payload = "testing" + req.transport_headers = {"test-headers": 'aaaa'} + output_text = self.transport.print_reproduceable(req) + self.assertIn("https://test.com", output_text) + + @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('requests.auth.HTTPBasicAuth') + def test_ibm_id_call(self, auth, request): + request.return_value = self.response + + data = ''' + +getObject + + + + +headers + + + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.transport_user = 'apikey' + req.transport_password = '1234567890qweasdzxc' + resp = self.transport(req) + + auth.assert_called_with('apikey', '1234567890qweasdzxc') + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_call_large_number_response(self, request): + response = requests.Response() + body = b''' + + + + + + + + + bytesUsed + 2666148982056 + + + + + + + + + ''' + response.raw = io.BytesIO(body) + response.headers['SoftLayer-Total-Items'] = 1 + response.status_code = 200 + request.return_value = response + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + resp = self.transport(req) + self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_nonascii_characters(self, request): + request.return_value = self.response + hostname = 'testé' + data = ''' + +getObject + + + + +headers + + + + + + + + +hostname +testé + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ({'hostname': hostname},) + req.transport_user = "testUser" + req.transport_password = "testApiKey" + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + + +@mock.patch('SoftLayer.transports.requests.Session.request') +@pytest.mark.parametrize( + "transport_verify,request_verify,expected", + [ + (True, True, True), + (True, False, False), + (True, None, True), + + (False, True, True), + (False, False, False), + (False, None, False), + + (None, True, True), + (None, False, False), + (None, None, True), + ] +) +def test_verify(request, + transport_verify, + request_verify, + expected): + request.return_value = get_xmlrpc_response() + + transport = transports.XmlRpcTransport( + endpoint_url='http://something9999999999999999999999.com', + ) + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + + if request_verify is not None: + req.verify = request_verify + + if transport_verify is not None: + transport.verify = transport_verify + + transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + data=mock.ANY, + headers=mock.ANY, + cert=mock.ANY, + proxies=mock.ANY, + timeout=mock.ANY, + verify=expected, + auth=None) + + + + + diff --git a/tools/requirements.txt b/tools/requirements.txt index dd85ec17e..880148ffd 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -4,3 +4,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 +zeep diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index ac58d1241..ab52a13fa 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,3 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 +zeep \ No newline at end of file From 6e4e5683fcbb0072c2dbf9c5c6a27846cbb2c6fe Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 6 Apr 2022 09:23:47 -0400 Subject: [PATCH 0289/1050] fix the team code review comments and fix the unit test --- SoftLayer/managers/network.py | 5 ++++- tests/managers/network_tests.py | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index bcb73f786..12d58fede 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -542,7 +542,10 @@ def list_vlans(self, datacenter=None, vlan_number=None, name=None, limit=100, ** kwargs['mask'] = DEFAULT_VLAN_MASK kwargs['iter'] = True - return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) + if limit > 0: + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), limit=limit) + else: + return self.account.getNetworkVlans(mask=kwargs['mask'], filter=_filter.to_dict(), iter=True) def list_securitygroups(self, **kwargs): """List security groups.""" diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..acb58bd21 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -360,13 +360,14 @@ def test_list_vlans_with_filters(self): self.assertEqual(result, fixtures.SoftLayer_Account.getNetworkVlans) _filter = { 'networkVlans': { - 'primaryRouter': { - 'datacenter': { - 'name': {'operation': '_= dal00'}}, - }, + 'id': { + 'operation': 'orderBy', + 'options': [ + {'name': 'sort', 'value': ['ASC']}]}, 'vlanNumber': {'operation': 5}, 'name': {'operation': '_= primary-vlan'}, - }, + 'primaryRouter': { + 'datacenter': {'name': {'operation': '_= dal00'}}}} } self.assert_called_with('SoftLayer_Account', 'getNetworkVlans', filter=_filter) From 9715c0be9c476c878815acba1c394b67f4c310be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 6 Apr 2022 17:39:16 -0500 Subject: [PATCH 0290/1050] objectMask support --- SoftLayer/transports/soap.py | 45 ++++++++++++++++++++-------------- tests/transports/soap_tests.py | 29 +++++++++++++++++++--- 2 files changed, 52 insertions(+), 22 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index b89fb090c..9a6eb499b 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -11,6 +11,7 @@ from zeep import Client, Settings, Transport, xsd from zeep.helpers import serialize_object from zeep.cache import SqliteCache +from zeep.plugins import HistoryPlugin import requests @@ -31,43 +32,51 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, # Throw an error for py < 3.6 because of f-strings logging.getLogger('zeep').setLevel(logging.ERROR) + logging.getLogger('zeep.transports').setLevel(logging.DEBUG) self.endpoint_url = (endpoint_url or consts.API_PUBLIC_ENDPOINT).rstrip('/') self.timeout = timeout or None self.proxy = proxy self.user_agent = user_agent or consts.USER_AGENT self.verify = verify self._client = None + self.history = HistoryPlugin() def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. :param request request: Request object """ - print("Making a SOAP API CALL...") - # client = Client(f"{self.endpoint_url}/{request.service}?wsdl") + zeep_settings = Settings(strict=False, xml_huge_tree=True) zeep_transport = Transport(cache=SqliteCache(timeout=86400)) client = Client(f"{self.endpoint_url}/{request.service}?wsdl", - settings=zeep_settings, transport=zeep_transport) - # authXsd = xsd.Element( - # f"{self.endpoint_url}/authenticate", - # xsd.ComplexType([ - # xsd.Element(f"{self.endpoint_url}/username", xsd.String()), - # xsd.Element(f"{self.endpoint_url}/apiKey", xsd.String()) - # ]) - # ) + settings=zeep_settings, transport=zeep_transport, plugins=[self.history]) + + # MUST define headers like this because otherwise the objectMask header doesn't work + # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( - '{http://api.softlayer.com/soap/v3.1/}authenticate', + '{http://api.softlayer.com/soap/v3/}authenticate', xsd.ComplexType([ - xsd.Element('{http://api.softlayer.com/soap/v3.1/}username', xsd.String()), - xsd.Element('{http://api.softlayer.com/soap/v3.1/}apiKey', xsd.String()) + xsd.Element('{http://api.service.softlayer.com/soap/v3/}username', xsd.String()), + xsd.Element('{http://api.service.softlayer.com/soap/v3/}apiKey', xsd.String()) ]) ) - # transport = Transport(session=get_session()) - - authHeader = xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + xsdMask = xsd.Element( + '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + xsd.ComplexType([ + xsd.Element('mask', xsd.String()), + ]) + ) + + headers = [ + xsdMask(mask=request.mask or ''), + xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + ] + + pp(headers) + print("HEADERS ^^^^^") method = getattr(client.service, request.method) - result = client.service.getObject(_soapheaders=[authHeader]) + result = client.service.getObject(_soapheaders=headers) return serialize_object(result) # result = transport.post(f"{self.endpoint_url}/{request.service}") @@ -80,4 +89,4 @@ def print_reproduceable(self, request): :param request request: Request object """ - return "THE SOAP API CALL..." + return self.history.last_sent diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index bfc0d4623..af52f2e98 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -39,21 +39,42 @@ def get_soap_response(): return response -class TestXmlRpcAPICall(testing.TestCase): +class TestSoapAPICall(testing.TestCase): def set_up(self): self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') self.response = get_soap_response() self.user = os.getenv('SL_USER') self.password = os.environ.get('SL_APIKEY') - - def test_call(self): request = Request() request.service = 'SoftLayer_Account' request.method = 'getObject' request.transport_user = self.user request.transport_password = self.password - data = self.transport(request) + self.request = request + + def test_call(self): + + data = self.transport(self.request) + pp(data) + self.assertEqual(data.get('id'), 307608) + self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") + + # def test_debug_call(self): + + # self.request.mask = "mask[id,accountName,companyName]" + # data = self.transport(self.request) + + # self.assertEqual(data.get('id'), 307608) + # debug_data = self.transport.print_reproduceable(self.request) + # print(debug_data['envelope']) + # self.assertEqual(":sdfsdf", debug_data) + + def test_objectMask(self): + self.request.mask = "mask[id,companyName]" + data = self.transport(self.request) pp(data) + self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") + self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) From 37f416cb1d2dbf9ce53cf94318d11c5a9aab140b Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 8 Apr 2022 10:58:25 -0400 Subject: [PATCH 0291/1050] solved comments --- SoftLayer/CLI/account/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 43d85b537..8d1048000 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -12,11 +12,11 @@ @click.option('--ack-all', is_flag=True, default=False, help="Acknowledge every upcoming event. Doing so will turn off the popup in the control portal") @click.option('--planned', is_flag=True, default=False, - help="Show just planned events") + help="Show only planned events") @click.option('--unplanned', is_flag=True, default=False, - help="Show just unplanned events") + help="Show only unplanned events") @click.option('--announcement', is_flag=True, default=False, - help="Show just announcement events") + help="Show only announcement events") @environment.pass_env def cli(env, ack_all, planned, unplanned, announcement): """Summary and acknowledgement of upcoming and ongoing maintenance events""" From 357ba70b96b3762a2d8336cb88b9eab1c9b263f3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Apr 2022 11:00:56 -0400 Subject: [PATCH 0292/1050] Update global ip assign/unassign to use new API --- SoftLayer/CLI/globalip/assign.py | 14 +++++++------- SoftLayer/fixtures/SoftLayer_Network_Subnet.py | 1 + SoftLayer/managers/network.py | 10 ++++++++++ tests/CLI/modules/globalip_tests.py | 2 +- tests/managers/network_tests.py | 4 ++++ 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py index ea9a3d12f..1595ccdba 100644 --- a/SoftLayer/CLI/globalip/assign.py +++ b/SoftLayer/CLI/globalip/assign.py @@ -5,17 +5,17 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers -@click.command() +@click.command(epilog="More information about types and ") @click.argument('identifier') -@click.argument('target') +@click.option('--target', + help='See SLDN docs. ' + 'E.g SoftLayer_Network_Subnet_IpAddress, SoftLayer_Hardware_Server,SoftLayer_Virtual_Guest') +@click.option('--router', help='An appropriate identifier for the specified $type. Some types have multiple identifier') @environment.pass_env -def cli(env, identifier, target): +def cli(env, identifier, target, router): """Assigns the global IP to a target.""" mgr = SoftLayer.NetworkManager(env.client) - global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, identifier, - name='global ip') - mgr.assign_global_ip(global_ip_id, target) + mgr.route(identifier, target, router) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index ac3b9d74a..d0b22b8e5 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,3 +44,4 @@ editNote = True setTags = True cancel = True +route = True \ No newline at end of file diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..73cd4430c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,13 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def route(self, subnet_id, type_serv, target): + """Assigns a global IP address to a specified target. + + :param int subnet_id: The ID of the global IP being assigned + :param string type_serv: The type service to assign + :param string target: The instance to assign + """ + return self.client.call('SoftLayer_Network_Subnet', 'route', + type_serv, target, id=subnet_id, ) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index e12b7c3f6..f46bd5ef7 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -15,7 +15,7 @@ class DnsTests(testing.TestCase): def test_ip_assign(self): - result = self.run_command(['globalip', 'assign', '1', '127.0.0.1']) + result = self.run_command(['globalip', 'assign', '1']) self.assert_no_fail(result) self.assertEqual(result.output, "") diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..24a2bbb0b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_route(self): + self.network.route('SoftLayer_Hardware_Server', 123456, 100) + self.assert_called_with('SoftLayer_Network_Subnet', 'route') From 86173b04641d0fe59a2f1f13a810a068e2e92b20 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 11 Apr 2022 15:42:38 -0400 Subject: [PATCH 0293/1050] fix the tox tool --- SoftLayer/fixtures/SoftLayer_Network_Subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index d0b22b8e5..9ecf8164e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,4 +44,4 @@ editNote = True setTags = True cancel = True -route = True \ No newline at end of file +route = True From d8f219d099adaab4c19082dce180647ab9244d9f Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 13 Apr 2022 12:10:34 -0400 Subject: [PATCH 0294/1050] Ability to route/unroute subnets --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/route.py | 26 +++++++++++++++++++ .../fixtures/SoftLayer_Network_Subnet.py | 1 + SoftLayer/managers/network.py | 10 +++++++ tests/CLI/modules/subnet_tests.py | 6 +++++ tests/managers/network_tests.py | 4 +++ 6 files changed, 48 insertions(+) create mode 100644 SoftLayer/CLI/subnet/route.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d5bb9d65..ac2d5084d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -323,6 +323,7 @@ ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), ('subnet:edit-ip', 'SoftLayer.CLI.subnet.edit_ip:cli'), + ('subnet:route', 'SoftLayer.CLI.subnet.route:cli'), ('tags', 'SoftLayer.CLI.tags'), ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), diff --git a/SoftLayer/CLI/subnet/route.py b/SoftLayer/CLI/subnet/route.py new file mode 100644 index 000000000..e4d4acd4e --- /dev/null +++ b/SoftLayer/CLI/subnet/route.py @@ -0,0 +1,26 @@ +"""allows you to change the route of your Account Owned subnets.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + +target_types = {'vlan': 'SoftLayer_Network_Vlan', + 'ip': 'SoftLayer_Network_Subnet_IpAddress', + 'hardware': 'SoftLayer_Hardware_Server', + 'vsi': 'SoftLayer_Virtual_Guest'} + + +@click.command(epilog="More information about types and identifiers " + "on https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/") +@click.argument('identifier') +@click.option('--target', type=click.Choice(['vlan', 'ip', 'hardware', 'vsi']), + help='choose the type. vlan, ip, hardware, vsi') +@click.option('--target-id', help='The identifier for the destination resource to route this subnet to. ') +@environment.pass_env +def cli(env, identifier, target, target_id): + """Assigns the subnet to a target.""" + + mgr = SoftLayer.NetworkManager(env.client) + mgr.route(identifier, target_types.get(target), target_id) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index ac3b9d74a..9ecf8164e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -44,3 +44,4 @@ editNote = True setTags = True cancel = True +route = True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..3fb1e1725 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,13 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def route(self, subnet_id, type_serv, target): + """Assigns a subnet to a specified target. + + :param int subnet_id: The ID of the global IP being assigned + :param string type_serv: The type service to assign + :param string target: The instance to assign + """ + return self.client.call('SoftLayer_Network_Subnet', 'route', + type_serv, target, id=subnet_id, ) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 65a4cc5c8..03166c9f9 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -184,3 +184,9 @@ def test_cancel(self, confirm_mock): def test_cancel_fail(self): result = self.run_command(['subnet', 'cancel', '1234']) self.assertEqual(result.exit_code, 2) + + def test_route(self): + result = self.run_command(['subnet', 'route', '1']) + + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..24a2bbb0b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_route(self): + self.network.route('SoftLayer_Hardware_Server', 123456, 100) + self.assert_called_with('SoftLayer_Network_Subnet', 'route') From 1e5e998e3c334fcbf55c699ebbfb28d5925a5dc6 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 13 Apr 2022 14:33:31 -0400 Subject: [PATCH 0295/1050] fix the tox analysis --- docs/cli/subnet.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 38bda428d..7d22b8246 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -30,3 +30,6 @@ Subnets .. click:: SoftLayer.CLI.subnet.edit_ip:cli :prog: subnet edit-ip :show-nested: +.. click:: SoftLayer.CLI.subnet.route:cli + :prog: subnet route + :show-nested: From afc6ec2cdb5cb9cd040d817fd1c80146279fa6bd Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 13 Apr 2022 14:53:02 -0500 Subject: [PATCH 0296/1050] #1602 got objectFilter kinda working, at least for simple things. Need to figure out how to deal with href entries though --- SoftLayer/transports/soap.py | 35 +++++++++++++++++++++++++++------- tests/transports/soap_tests.py | 12 ++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 9a6eb499b..1d2ab4284 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -40,6 +40,7 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, self.verify = verify self._client = None self.history = HistoryPlugin() + self.soapNS = "http://api.service.softlayer.com/soap/v3.1/" def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. @@ -55,29 +56,49 @@ def __call__(self, request): # MUST define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( - '{http://api.softlayer.com/soap/v3/}authenticate', + f"{{{self.soapNS}}}authenticate", xsd.ComplexType([ - xsd.Element('{http://api.service.softlayer.com/soap/v3/}username', xsd.String()), - xsd.Element('{http://api.service.softlayer.com/soap/v3/}apiKey', xsd.String()) + xsd.Element(f'{{{self.soapNS}}}username', xsd.String()), + xsd.Element(f'{{{self.soapNS}}}apiKey', xsd.String()) ]) ) + factory = client.type_factory(f"{self.soapNS}") + theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") xsdMask = xsd.Element( '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + factory['SoftLayer_ObjectMask'] + ) + + # Object Filter + filterType = client.get_type(f"{{{self.soapNS}}}{request.service}ObjectFilter") + xsdFilter = xsd.Element( + f"{{{self.soapNS}}}{request.service}ObjectFilter", filterType + ) + + # Result Limit + xsdResultLimit = xsd.Element( + f"{{{self.soapNS}}}resultLimit", xsd.ComplexType([ - xsd.Element('mask', xsd.String()), + xsd.Element('limit', xsd.String()), + xsd.Element('offset', xsd.String()), ]) ) + test = {"type":{"keyName":{"operation":"BARE_METAL_CPU"}} } headers = [ xsdMask(mask=request.mask or ''), - xsdUserAuth(username=request.transport_user, apiKey=request.transport_password) + xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), + xsdResultLimit(limit=2, offset=0), + xsdFilter(**request.filter or '') # The ** here forces python to treat this dict as properties ] pp(headers) print("HEADERS ^^^^^") method = getattr(client.service, request.method) - result = client.service.getObject(_soapheaders=headers) - return serialize_object(result) + + # result = client.service.getObject(_soapheaders=headers) + result = method(_soapheaders=headers) + return serialize_object(result['body']['getAllObjectsReturn']) # result = transport.post(f"{self.endpoint_url}/{request.service}") diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index af52f2e98..df86321c2 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -78,3 +78,15 @@ def test_objectMask(self): self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) + def test_objectFilter(self): + self.request.service = "SoftLayer_Product_Package" + self.request.method = "getAllObjects" + self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" + self.request.filter = {'type': {'keyName': {'operation': 'BARE_METAL_CPU'}}} + data = self.transport(self.request) + # pp(data) + # print("^^^ DATA **** ") + for package in data: + pp(package) + print("^^^ PACKAGE **** ") + self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") \ No newline at end of file From b32b8a979ad5b2115ac00682ea6aa1c3ee0e46b9 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 14 Apr 2022 08:33:10 -0400 Subject: [PATCH 0297/1050] fix the team code review comments --- SoftLayer/CLI/globalip/assign.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/globalip/assign.py b/SoftLayer/CLI/globalip/assign.py index 1595ccdba..3f58d7125 100644 --- a/SoftLayer/CLI/globalip/assign.py +++ b/SoftLayer/CLI/globalip/assign.py @@ -6,16 +6,21 @@ import SoftLayer from SoftLayer.CLI import environment +target_types = {'vlan': 'SoftLayer_Network_Vlan', + 'ip': 'SoftLayer_Network_Subnet_IpAddress', + 'hardware': 'SoftLayer_Hardware_Server', + 'vsi': 'SoftLayer_Virtual_Guest'} -@click.command(epilog="More information about types and ") + +@click.command(epilog="More information about types and identifiers " + "on https://sldn.softlayer.com/reference/services/SoftLayer_Network_Subnet/route/") @click.argument('identifier') -@click.option('--target', - help='See SLDN docs. ' - 'E.g SoftLayer_Network_Subnet_IpAddress, SoftLayer_Hardware_Server,SoftLayer_Virtual_Guest') -@click.option('--router', help='An appropriate identifier for the specified $type. Some types have multiple identifier') +@click.option('--target', type=click.Choice(['vlan', 'ip', 'hardware', 'vsi']), + help='choose the type. vlan, ip, hardware, vsi') +@click.option('--target-id', help='The identifier for the destination resource to route this subnet to. ') @environment.pass_env -def cli(env, identifier, target, router): - """Assigns the global IP to a target.""" +def cli(env, identifier, target, target_id): + """Assigns the subnet to a target.""" mgr = SoftLayer.NetworkManager(env.client) - mgr.route(identifier, target, router) + mgr.route(identifier, target_types.get(target), target_id) From 1e47c3401ff64dd1096a7a057b18b62ecddfa972 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Tue, 19 Apr 2022 09:59:57 -0400 Subject: [PATCH 0298/1050] Improved successful response to command - slcli account cancel-item --- SoftLayer/CLI/account/cancel_item.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py index 0cc08593d..626643446 100644 --- a/SoftLayer/CLI/account/cancel_item.py +++ b/SoftLayer/CLI/account/cancel_item.py @@ -15,4 +15,5 @@ def cli(env, identifier): manager = AccountManager(env.client) item = manager.cancel_item(identifier) - env.fout(item) + if item: + env.fout("Item: {} was cancelled.".format(identifier)) From d4aac0d8306bc3b3fe95b543837aef9ea1b6c487 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 20 Apr 2022 10:54:45 -0400 Subject: [PATCH 0299/1050] Improved successful response to command - slcli virtual edit --- SoftLayer/CLI/virt/edit.py | 13 +++++++++---- tests/CLI/modules/vs/vs_tests.py | 8 +++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/edit.py b/SoftLayer/CLI/virt/edit.py index a72caa585..93f9c6694 100644 --- a/SoftLayer/CLI/virt/edit.py +++ b/SoftLayer/CLI/virt/edit.py @@ -54,11 +54,16 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, vsi = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - if not vsi.edit(vs_id, **data): - raise exceptions.CLIAbort("Failed to update virtual server") + + if vsi.edit(vs_id, **data): + for key, value in data.items(): + if value is not None: + env.fout("The {} of virtual server instance: {} was updated.".format(key, vs_id)) if public_speed is not None: - vsi.change_port_speed(vs_id, True, int(public_speed)) + if vsi.change_port_speed(vs_id, True, int(public_speed)): + env.fout("The public speed of virtual server instance: {} was updated.".format(vs_id)) if private_speed is not None: - vsi.change_port_speed(vs_id, False, int(private_speed)) + if vsi.change_port_speed(vs_id, False, int(private_speed)): + env.fout("The private speed of virtual server instance: {} was updated.".format(vs_id)) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 4ae31fd6d..99fafc0bf 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -609,7 +609,13 @@ def test_edit(self): '100']) self.assert_no_fail(result) - self.assertEqual(result.output, '') + expected_output = '"The userdata of virtual server instance: 100 was updated."\n' \ + + '"The hostname of virtual server instance: 100 was updated."\n' \ + + '"The domain of virtual server instance: 100 was updated."\n' \ + + '"The tags of virtual server instance: 100 was updated."\n' \ + + '"The public speed of virtual server instance: 100 was updated."\n' \ + + '"The private speed of virtual server instance: 100 was updated."\n' + self.assertEqual(result.output, expected_output) self.assert_called_with( 'SoftLayer_Virtual_Guest', 'editObject', From 53acb2aefe2605a700a5a341f394921d81b85c2a Mon Sep 17 00:00:00 2001 From: edsonarios Date: Wed, 20 Apr 2022 16:28:11 -0400 Subject: [PATCH 0300/1050] Improved successful response to command - slcli vlan cancel --- SoftLayer/CLI/vlan/cancel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 35f5aa0f9..3470c0599 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -25,10 +25,11 @@ def cli(env, identifier): raise exceptions.CLIAbort(reasons) item = mgr.get_vlan(identifier).get('billingItem') if item: - mgr.cancel_item(item.get('id'), - True, - 'Cancel by cli command', - 'Cancel by cli command') + if mgr.cancel_item(item.get('id'), + True, + 'Cancel by cli command', + 'Cancel by cli command'): + env.fout("VLAN {} was cancelled.".format(identifier)) else: raise exceptions.CLIAbort( "VLAN is an automatically assigned and free of charge VLAN," From 955eacb7ebb57ccc889e01333ee20b2fe3591d97 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 20 Apr 2022 16:52:39 -0500 Subject: [PATCH 0301/1050] got filters working, need to upload changes to zeep --- SoftLayer/transports/soap.py | 52 +++++++++++++++++++++++++++------- tests/transports/soap_tests.py | 9 ++++-- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 1d2ab4284..76fa33a8c 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -12,6 +12,8 @@ from zeep.helpers import serialize_object from zeep.cache import SqliteCache from zeep.plugins import HistoryPlugin +from zeep.wsdl.messages.multiref import process_multiref + import requests @@ -53,6 +55,8 @@ def __call__(self, request): client = Client(f"{self.endpoint_url}/{request.service}?wsdl", settings=zeep_settings, transport=zeep_transport, plugins=[self.history]) + # print(client.wsdl.dump()) + # print("=============== WSDL ==============") # MUST define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. xsdUserAuth = xsd.Element( @@ -65,7 +69,7 @@ def __call__(self, request): factory = client.type_factory(f"{self.soapNS}") theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") xsdMask = xsd.Element( - '{http://api.service.softlayer.com/soap/v3.1/}SoftLayer_ObjectMask', + f"{{{self.soapNS}}}SoftLayer_ObjectMask", factory['SoftLayer_ObjectMask'] ) @@ -84,22 +88,48 @@ def __call__(self, request): ]) ) - test = {"type":{"keyName":{"operation":"BARE_METAL_CPU"}} } + # Might one day want to support unauthenticated requests, but for now assume user auth. headers = [ - xsdMask(mask=request.mask or ''), xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), - xsdResultLimit(limit=2, offset=0), - xsdFilter(**request.filter or '') # The ** here forces python to treat this dict as properties ] - pp(headers) - print("HEADERS ^^^^^") - method = getattr(client.service, request.method) + if request.limit: + headers.append(xsdResultLimit(limit=request.limit, offset=request.offset)) + if request.mask: + headers.append(xsdMask(mask=request.mask)) + if request.filter: + # The ** here forces python to treat this dict as properties + headers.append(xsdFilter(**request.filter)) + + + try: + method = getattr(client.service, request.method) + except AttributeError as ex: + message = f"{request.service}::{request.method}() does not exist in {self.soapNS}{request.service}?wsdl" + raise exceptions.TransportError(404, message) from ex - # result = client.service.getObject(_soapheaders=headers) result = method(_soapheaders=headers) - return serialize_object(result['body']['getAllObjectsReturn']) - # result = transport.post(f"{self.endpoint_url}/{request.service}") + # result = client.service.getObject(_soapheaders=headers) + + # process_multiref(result['body']['getAllObjectsReturn']) + + # print("^^^ RESULT ^^^^^^^") + + # TODO GET A WAY TO FIND TOTAL ITEMS + # print(result['header']['totalItems']['amount']) + # print(" ^^ ITEMS ^^^ ") + + try: + methodReturn = f"{request.method}Return" + serialize = serialize_object(result) + if serialize.get('body'): + return serialize['body'][methodReturn] + else: + # Some responses (like SoftLayer_Account::getObject) don't have a body? + return serialize + except KeyError as e: + message = f"Error serializeing response\n{result}\n" + raise exceptions.TransportError(500, message) def print_reproduceable(self, request): diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index df86321c2..bd98733ad 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -83,10 +83,13 @@ def test_objectFilter(self): self.request.method = "getAllObjects" self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" self.request.filter = {'type': {'keyName': {'operation': 'BARE_METAL_CPU'}}} + self.request.limit = 5 + self.request.offset = 0 data = self.transport(self.request) # pp(data) # print("^^^ DATA **** ") for package in data: - pp(package) - print("^^^ PACKAGE **** ") - self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") \ No newline at end of file + + self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") + + ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file From 34f6dafb5584ba068a86a26c0bb8df433ad26359 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 21 Apr 2022 16:21:45 -0500 Subject: [PATCH 0302/1050] #1602 initParams working --- SoftLayer/transports/soap.py | 11 +++++++++++ tests/transports/soap_tests.py | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 76fa33a8c..7f985fc02 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -102,6 +102,17 @@ def __call__(self, request): headers.append(xsdFilter(**request.filter)) + + if request.identifier: + initParam = f"{request.service}InitParameters" + initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") + xsdInitParam = xsd.Element( + f"{{{self.soapNS}}}{initParam}", initParamType + ) + # Might want to check if its an id or globalIdentifier at some point, for now only id. + headers.append(xsdInitParam(id=request.identifier)) + + # TODO Add params... maybe try: method = getattr(client.service, request.method) except AttributeError as ex: diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index bd98733ad..51849dc5c 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -92,4 +92,27 @@ def test_objectFilter(self): self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") + def test_virtualGuest(self): + accountRequest = Request() + accountRequest.service = "SoftLayer_Account" + accountRequest.method = "getVirtualGuests" + accountRequest.limit = 5 + accountRequest.offset = 0 + accountRequest.mask = "mask[id,hostname,domain]" + accountRequest.transport_user = self.user + accountRequest.transport_password = self.password + + vsis = self.transport(accountRequest) + for vsi in vsis: + self.assertGreater(vsi.get('id'), 1) + vsiRequest = Request() + vsiRequest.service = "SoftLayer_Virtual_Guest" + vsiRequest.method = "getObject" + vsiRequest.identifier = vsi.get('id') + vsiRequest.mask = "mask[id,hostname,domain]" + vsiRequest.transport_user = self.user + vsiRequest.transport_password = self.password + thisVsi = self.transport(vsiRequest) + self.assertEqual(thisVsi.get('id'), vsi.get('id')) + ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file From 6da51a38588ebeb0eb99a53799016a5ae3700cb8 Mon Sep 17 00:00:00 2001 From: edsonarios Date: Fri, 22 Apr 2022 10:58:07 -0400 Subject: [PATCH 0303/1050] solved parameter domain to domainName and change result from None to emtpy --- SoftLayer/CLI/account/item_detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index ddc2d31ed..7aac9a963 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -28,7 +28,7 @@ def item_table(item): table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) table.add_row(['description', item.get('description')]) table.align = 'l' - fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + fqdn = "{}.{}".format(item.get('hostName', ''), item.get('domainName', '')) if fqdn != ".": table.add_row(['FQDN', fqdn]) From d6ef5619f1bf589f152912513908abc9e7c2ecc7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 22 Apr 2022 23:19:49 -0400 Subject: [PATCH 0304/1050] slcli autoscale create --- SoftLayer/CLI/autoscale/create.py | 137 ++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Scale_Group.py | 76 +++++++++++ SoftLayer/managers/autoscale.py | 11 ++ SoftLayer/managers/network.py | 12 ++ docs/cli/autoscale.rst | 4 + tests/CLI/modules/autoscale_tests.py | 24 +++- tests/managers/autoscale_tests.py | 51 ++++++++ tests/managers/network_tests.py | 4 + 9 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/autoscale/create.py diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py new file mode 100644 index 000000000..86f17ddb8 --- /dev/null +++ b/SoftLayer/CLI/autoscale/create.py @@ -0,0 +1,137 @@ +"""Order/create a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.managers.autoscale import AutoScaleManager + + +@click.command() +@click.option('--name', help="Scale group's name.") +@click.option('--cooldown', type=click.INT, + help="The number of seconds this group will wait after lastActionDate before performing another action.") +@click.option('--min', 'minimum', type=click.INT, help="Set the minimum number of guests") +@click.option('--max', 'maximum', type=click.INT, help="Set the maximum number of guests") +@click.option('--regional', type=click.INT, + help="The identifier of the regional group this scaling group is assigned to.") +@click.option('--postinstall', '-i', help="Post-install script to download") +@click.option('--os', '-o', help="OS install code. Tip: you can specify _LATEST") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--domain', '-D', required=True, prompt=True, help="Domain portion of the FQDN") +@click.option('--cpu', type=click.INT, help="Number of CPUs for new guests (existing not effected") +@click.option('--memory', type=click.INT, help="RAM in MB or GB for new guests (existing not effected") +@click.option('--policy-relative', help="The type of scale to perform(ABSOLUTE, PERCENT, RELATIVE).") +@click.option('--termination-policy', + help="The termination policy for the group(CLOSEST_TO_NEXT_CHARGE=1, NEWEST=2, OLDEST=3).") +@click.option('--policy-name', help="Collection of policies for this group. This can be empty.") +@click.option('--policy-amount', help="The number to scale by. This number has different meanings based on type.") +@click.option('--userdata', help="User defined metadata string") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@helpers.multi_option('--disk', help="Disk sizes") +@environment.pass_env +def cli(env, **args): + """Order/create autoscale.""" + scale = AutoScaleManager(env.client) + network = SoftLayer.NetworkManager(env.client) + + pods = network.get_closed_pods() + closure = [] + + datacenter = network.get_datacenter(args.get('datacenter')) + + ssh_keys = [] + for key in args.get('key'): + resolver = SoftLayer.SshKeyManager(env.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') + ssh_keys.append(key_id) + scale_actions = [ + { + "amount": args['policy_amount'], + "scaleType": args['policy_relative'] + } + ] + policy_template = { + 'name': args['policy_name'], + 'policies': scale_actions + + } + policies = [] + + block = [] + number_disk = 0 + for guest_disk in args['disk']: + disks = {'diskImage': {'capacity': guest_disk}, 'device': number_disk} + block.append(disks) + number_disk += 1 + + virt_template = { + 'localDiskFlag': False, + 'domain': args['domain'], + 'hostname': args['hostname'], + 'sshKeys': ssh_keys, + 'postInstallScriptUri': args.get('postinstall'), + 'operatingSystemReferenceCode': args['os'], + 'maxMemory': args.get('memory'), + 'datacenter': {'id': datacenter[0]['id']}, + 'startCpus': args.get('cpu'), + 'blockDevices': block, + 'hourlyBillingFlag': True, + 'privateNetworkOnlyFlag': False, + 'networkComponents': [{'maxSpeed': 100}], + 'typeId': 1, + 'userData': [{ + 'value': args.get('userdata') + }], + 'networkVlans': [], + + } + + order = { + 'name': args['name'], + 'cooldown': args['cooldown'], + 'maximumMemberCount': args['maximum'], + 'minimumMemberCount': args['minimum'], + 'regionalGroupId': args['regional'], + 'suspendedFlag': False, + 'balancedTerminationFlag': False, + 'virtualGuestMemberTemplate': virt_template, + 'virtualGuestMemberCount': 0, + 'policies': policies.append(clean_dict(policy_template)), + 'terminationPolicyId': args['termination_policy'] + } + + # print(virt_template) + + for pod in pods: + if args.get('datacenter') in str(pod['name']): + closure.append(pod['name']) + click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborting scale group order.') + else: + result = scale.create(order) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', result['id']]) + table.add_row(['Created', result['createDate']]) + table.add_row(['Name', result['name']]) + table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) + table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) + table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) + output = table + + env.fout(output) + + +def clean_dict(dictionary): + """Removes any `None` entires from the dictionary""" + return {k: v for k, v in dictionary.items() if v} diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2d5bb9d65..705ac8b10 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -372,6 +372,7 @@ ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), + ('autoscale:create', 'SoftLayer.CLI.autoscale.create:cli'), ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index f04d8f56e..6b0ce3db6 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -455,3 +455,79 @@ ] editObject = True + +createObject = { + "accountId": 307608, + "cooldown": 3600, + "createDate": "2022-04-22T13:45:24-06:00", + "id": 5446140, + "lastActionDate": "2022-04-22T13:45:29-06:00", + "maximumMemberCount": 5, + "minimumMemberCount": 1, + "name": "test22042022", + "regionalGroupId": 4568, + "suspendedFlag": False, + "terminationPolicyId": 2, + "virtualGuestMemberTemplate": { + "accountId": 307608, + "domain": "test.com", + "hostname": "testvs", + "maxMemory": 2048, + "startCpus": 2, + "blockDevices": [ + { + "diskImage": { + "capacity": 100, + } + } + ], + "hourlyBillingFlag": True, + "localDiskFlag": True, + "networkComponents": [ + { + "maxSpeed": 100, + } + ], + "operatingSystemReferenceCode": "CENTOS_7_64", + "userData": [ + { + "value": "the userData" + } + ] + }, + "virtualGuestMemberCount": 0, + "networkVlans": [], + "policies": [], + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [ + { + "createDate": "2022-04-22T13:45:29-06:00", + "id": 123456, + "scaleGroupId": 5446140, + "virtualGuest": { + "createDate": "2022-04-22T13:45:28-06:00", + "deviceStatusId": 3, + "domain": "test.com", + "fullyQualifiedDomainName": "testvs-97e7.test.com", + "hostname": "testvs-97e7", + "id": 129911702, + "maxCpu": 2, + "maxCpuUnits": "CORE", + "maxMemory": 2048, + "startCpus": 2, + "statusId": 1001, + "typeId": 1, + "uuid": "46e55f99-b412-4287-95b5-b8182b2fc924", + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + } + } + ] +} diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 78fa18e31..e32eb9f35 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -116,3 +116,14 @@ def edit(self, identifier, template): .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ """ return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) + + def create(self, template): + """Calls `SoftLayer_Scale_Group::createObject()`_ + + :param template: `SoftLayer_Scale_Group`_ + + .. _SoftLayer_Scale_Group::createObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/createObject/ + .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ + """ + return self.client.call('SoftLayer_Scale_Group', 'createObject', template) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0f550ec3d..0128abb51 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -824,3 +824,15 @@ def get_closed_pods(self): mask = """mask[name, datacenterLongName, frontendRouterId, capabilities, datacenterId, backendRouterId, backendRouterName, frontendRouterName]""" return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', mask=mask, filter=closing_filter) + + def get_datacenter(self, _filter=None, datacenter=None): + """Calls SoftLayer_Location::getDatacenters() + + returns datacenter list. + """ + _filter = None + + if datacenter: + _filter = {"name": {"operation": datacenter}} + + return self.client.call('SoftLayer_Location', 'getDatacenters', filter=_filter, limit=1) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index a3aa31462..2e2292ffd 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -34,5 +34,9 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :prog: autoscale edit :show-nested: +.. click:: SoftLayer.CLI.autoscale.create:cli + :prog: autoscale create + :show-nested: + .. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale \ No newline at end of file diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index cb3cdfdb9..cc9d3b9f4 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -77,7 +77,7 @@ def test_autoscale_edit_userdata(self, manager): @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') def test_autoscale_edit_userfile(self, manager): # On windows, python cannot edit a NamedTemporaryFile. - if(sys.platform.startswith("win")): + if (sys.platform.startswith("win")): self.skipTest("Test doesn't work in Windows") group = fixtures.SoftLayer_Scale_Group.getObject template = { @@ -89,3 +89,25 @@ def test_autoscale_edit_userfile(self, manager): result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) self.assert_no_fail(result) manager.assert_called_with('12345', template) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_autoscale_create(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['autoscale', 'create', + '--name=test', + '--cooldown=3600', + '--min=1', + '--max=3', + '-o=CENTOS_7_64', + '--datacenter=ams01', + '--termination-policy=2', + '-H=testvs', + '-D=test.com', + '--cpu=2', + '--memory=1024', + '--policy-relative=absolute', + '--policy-amount=3', + '--regional=102', + '--disk=25']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 6da505409..febd62606 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -123,3 +123,54 @@ def test_edit_object(self): 'editObject', args=(template,), identifier=12345) + + def test_create_object(self): + template = { + 'name': 'test', + 'cooldown': 3600, + 'maximumMemberCount': 5, + 'minimumMemberCount': 1, + 'regionalGroupId': 4568, + 'suspendedFlag': False, + 'balancedTerminationFlag': False, + 'virtualGuestMemberTemplate': { + 'domain': 'test.com', + 'hostname': 'testvs', + 'operatingSystemReferenceCode': 'CENTOS_7_64', + 'maxMemory': 2048, + 'datacenter': { + 'id': 265592 + }, + 'startCpus': 2, + 'blockDevices': [ + { + 'diskImage': { + 'capacity': '100' + }, + 'device': 0 + } + ], + 'hourlyBillingFlag': True, + 'networkComponents': [ + { + 'maxSpeed': 100 + } + ], + 'localDiskFlag': True, + 'typeId': 1, + 'userData': [ + { + 'value': 'the userData' + } + ] + }, + 'virtualGuestMemberCount': 0, + + 'terminationPolicyId': '2', + } + + self.autoscale.create(template) + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'createObject', + args=(template,)) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a578fd604..df145ed67 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -628,3 +628,7 @@ def test_vlan_edit(self): def test_get_all_pods(self): self.network.get_pods() self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') + + def test_get_all_datacenter(self): + self.network.get_datacenter() + self.assert_called_with('SoftLayer_Location', 'getDatacenters') From 7da2e4dd1ae1a219d4903fd8840dc532a192f56c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 25 Apr 2022 14:58:15 -0500 Subject: [PATCH 0305/1050] AutoPep8 fixes --- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/fixtures/SoftLayer_Billing_Order.py | 2 +- .../fixtures/SoftLayer_Network_Storage.py | 2 +- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 14 ++--- .../fixtures/SoftLayer_Product_Package.py | 4 +- .../SoftLayer_Software_AccountLicense.py | 52 +++++++++---------- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 2 +- SoftLayer/managers/vs.py | 4 +- SoftLayer/transports/__init__.py | 10 ---- SoftLayer/transports/debug.py | 2 +- SoftLayer/transports/fixture.py | 3 +- SoftLayer/transports/rest.py | 2 +- SoftLayer/transports/soap.py | 9 ++-- SoftLayer/transports/transport.py | 2 + SoftLayer/transports/xmlrpc.py | 1 + tests/CLI/modules/server_tests.py | 36 ++++++------- tests/CLI/modules/vs/vs_tests.py | 36 ++++++------- tests/managers/hardware_tests.py | 6 +-- tests/transport_tests.py | 1 - tests/transports/rest_tests.py | 3 +- tests/transports/soap_tests.py | 8 +-- tests/transports/xmlrpc_tests.py | 5 -- 22 files changed, 98 insertions(+), 108 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index fb5aedb67..ec5457a72 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1173,7 +1173,7 @@ "virtualizationPlatform": 0, "requiredUser": "administrator@vsphere.local" } - } +} ] getActiveVirtualLicenses = [{ diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py index ae35280ea..6136ece3e 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -44,7 +44,7 @@ ], 'orderApprovalDate': '2019-09-15T13:13:13-06:00', 'orderTotalAmount': '0' - }] +}] getObject = { 'accountId': 1234, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index bf1f7adc4..acf369d16 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -237,7 +237,7 @@ } refreshDuplicate = { - 'dependentDuplicate': 1 + 'dependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 960c98995..087d854af 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -7,13 +7,13 @@ 'vlanNumber': 4444, 'firewallInterfaces': None, 'billingItem': { - 'allowCancellationFlag': 1, - 'categoryCode': 'network_vlan', - 'description': 'Private Network Vlan', - 'id': 235689, - 'notes': 'test cli', - 'orderItemId': 147258, - } + 'allowCancellationFlag': 1, + 'categoryCode': 'network_vlan', + 'description': 'Private Network Vlan', + 'id': 235689, + 'notes': 'test cli', + 'orderItemId': 147258, + } } editObject = True diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index c4c45985d..d2a93d89c 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2137,7 +2137,7 @@ "recurringFee": "0", "setupFee": "0", "sort": 10, - }] + }] }, { "description": "Public Network Vlan", "id": 1071, @@ -2168,6 +2168,6 @@ "recurringFee": "0", "setupFee": "0", "sort": 10, - }] + }] } ] diff --git a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py index b4433d104..893a6921a 100644 --- a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py +++ b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py @@ -1,30 +1,30 @@ getAllObjects = [{ - "capacity": "4", - "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", - "units": "CPU", - "billingItem": { - "allowCancellationFlag": 1, - "categoryCode": "software_license", - "createDate": "2018-10-22T11:16:48-06:00", - "cycleStartDate": "2021-06-03T23:11:22-06:00", - "description": "vCenter Server Appliance 6.0", - "id": 123654789, - "lastBillDate": "2021-06-03T23:11:22-06:00", - "modifyDate": "2021-06-03T23:11:22-06:00", - "nextBillDate": "2021-07-03T23:00:00-06:00", - "orderItemId": 385054741, - "recurringMonths": 1, - "serviceProviderId": 1, - }, - "softwareDescription": { - "id": 1529, - "longDescription": "VMware vCenter 6.0", - "manufacturer": "VMware", - "name": "vCenter", - "version": "6.0", - "requiredUser": "administrator@vsphere.local" - } + "capacity": "4", + "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2018-10-22T11:16:48-06:00", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 123654789, + "lastBillDate": "2021-06-03T23:11:22-06:00", + "modifyDate": "2021-06-03T23:11:22-06:00", + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 385054741, + "recurringMonths": 1, + "serviceProviderId": 1, }, + "softwareDescription": { + "id": 1529, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "version": "6.0", + "requiredUser": "administrator@vsphere.local" + } +}, { "capacity": "1", "key": "CBERT-4RL92-K8999-031K4-AJF5J", @@ -48,4 +48,4 @@ "name": "Virtual SAN Advanced Tier III", "version": "6.2", } - }] +}] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index f7e422d22..3c472b8d2 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -317,7 +317,7 @@ 'item': {'description': '2 GB'}, 'hourlyRecurringFee': '.06', 'recurringFee': '42' - }, + }, 'template': {'maxMemory': 2048} }, { diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 2fa698dce..403cf4dcb 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1067,8 +1067,8 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr category = {'categories': [{ 'categoryCode': 'guest_disk' + str(disk_number), 'complexType': "SoftLayer_Product_Item_Category"}], - 'complexType': 'SoftLayer_Product_Item_Price', - 'id': price_id} + 'complexType': 'SoftLayer_Product_Item_Price', + 'id': price_id} prices.append(category) order['prices'] = prices diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py index bbecea227..454f32997 100644 --- a/SoftLayer/transports/__init__.py +++ b/SoftLayer/transports/__init__.py @@ -33,13 +33,3 @@ 'FixtureTransport', 'SoftLayerListResult' ] - - - - - - - - - - diff --git a/SoftLayer/transports/debug.py b/SoftLayer/transports/debug.py index 31b93b847..28ad67310 100644 --- a/SoftLayer/transports/debug.py +++ b/SoftLayer/transports/debug.py @@ -58,4 +58,4 @@ def get_last_calls(self): def print_reproduceable(self, call): """Prints a reproduceable debugging output""" - return self.transport.print_reproduceable(call) \ No newline at end of file + return self.transport.print_reproduceable(call) diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py index 3eece28fc..2fa016665 100644 --- a/SoftLayer/transports/fixture.py +++ b/SoftLayer/transports/fixture.py @@ -8,6 +8,7 @@ import importlib + class FixtureTransport(object): """Implements a transport which returns fixtures.""" @@ -27,4 +28,4 @@ def __call__(self, call): def print_reproduceable(self, call): """Not Implemented""" - return call.service \ No newline at end of file + return call.service diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py index e80d5bb35..3af9c1e40 100644 --- a/SoftLayer/transports/rest.py +++ b/SoftLayer/transports/rest.py @@ -179,4 +179,4 @@ def print_reproduceable(self, request): headers = ['"{0}: {1}"'.format(k, v) for k, v in request.transport_headers.items()] headers = " -H ".join(headers) - return command.format(method=method, headers=headers, data=data, uri=request.url) \ No newline at end of file + return command.format(method=method, headers=headers, data=data, uri=request.url) diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 7f985fc02..585e4e12d 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -27,6 +27,8 @@ from .transport import SoftLayerListResult from pprint import pprint as pp + + class SoapTransport(object): """XML-RPC transport.""" @@ -101,8 +103,6 @@ def __call__(self, request): # The ** here forces python to treat this dict as properties headers.append(xsdFilter(**request.filter)) - - if request.identifier: initParam = f"{request.service}InitParameters" initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") @@ -121,7 +121,7 @@ def __call__(self, request): result = method(_soapheaders=headers) # result = client.service.getObject(_soapheaders=headers) - + # process_multiref(result['body']['getAllObjectsReturn']) # print("^^^ RESULT ^^^^^^^") @@ -142,7 +142,6 @@ def __call__(self, request): message = f"Error serializeing response\n{result}\n" raise exceptions.TransportError(500, message) - def print_reproduceable(self, request): """Prints out the minimal python code to reproduce a specific request @@ -150,5 +149,5 @@ def print_reproduceable(self, request): :param request request: Request object """ - + return self.history.last_sent diff --git a/SoftLayer/transports/transport.py b/SoftLayer/transports/transport.py index 40a8e872b..e795b3ecc 100644 --- a/SoftLayer/transports/transport.py +++ b/SoftLayer/transports/transport.py @@ -105,6 +105,7 @@ def __repr__(self): return "{service}::{method}({params})".format( service=self.service, method=self.method, params=param_string) + class SoftLayerListResult(list): """A SoftLayer API list result.""" @@ -115,6 +116,7 @@ def __init__(self, items=None, total_count=0): self.total_count = total_count super().__init__(items) + def _proxies_dict(proxy): """Makes a proxy dict appropriate to pass to requests.""" if not proxy: diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py index 31afaf868..c10481812 100644 --- a/SoftLayer/transports/xmlrpc.py +++ b/SoftLayer/transports/xmlrpc.py @@ -21,6 +21,7 @@ from .transport import get_session from .transport import SoftLayerListResult + class XmlRpcTransport(object): """XML-RPC transport.""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index b026fa8cf..8e7382e16 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -693,19 +693,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -748,12 +748,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 4ae31fd6d..35dd2e4ac 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -345,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -400,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a9eada76c..58eac87d7 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,9 +557,9 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', },), identifier=100) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 5ae0a448c..0c175df5c 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -18,7 +18,6 @@ from SoftLayer import transports - class TestFixtureTransport(testing.TestCase): def set_up(self): diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index ec634ec6c..f0c69cea0 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -17,6 +17,7 @@ from SoftLayer import testing from SoftLayer import transports + class TestRestAPICall(testing.TestCase): def set_up(self): @@ -362,4 +363,4 @@ def test_complex_encoder_bytes(self): result = json.dumps(to_encode, cls=transports.transport.ComplexEncoder) # result = '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}' # encode doesn't always encode in the same order, so testing exact match SOMETIMES breaks. - self.assertIn("QVNEQVNEQVNE", result) \ No newline at end of file + self.assertIn("QVNEQVNEQVNE", result) diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index 51849dc5c..d4b0bf335 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -20,7 +20,9 @@ from SoftLayer.transports import Request -from pprint import pprint as pp +from pprint import pprint as pp + + def get_soap_response(): response = requests.Response() list_body = b''' @@ -64,7 +66,7 @@ def test_call(self): # self.request.mask = "mask[id,accountName,companyName]" # data = self.transport(self.request) - + # self.assertEqual(data.get('id'), 307608) # debug_data = self.transport.print_reproduceable(self.request) # print(debug_data['envelope']) @@ -115,4 +117,4 @@ def test_virtualGuest(self): thisVsi = self.transport(vsiRequest) self.assertEqual(thisVsi.get('id'), vsi.get('id')) - ## TODO MORE COMPLEX OBJECT FILTERS! \ No newline at end of file + # TODO MORE COMPLEX OBJECT FILTERS! diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index c59eded0c..8852a327d 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -460,8 +460,3 @@ def test_verify(request, timeout=mock.ANY, verify=expected, auth=None) - - - - - From 8616d3ebf6fb7bbc965a15a3b98f5c33b524c905 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 25 Apr 2022 16:19:05 -0500 Subject: [PATCH 0306/1050] fixed tox style issues --- SoftLayer/transports/__init__.py | 13 ++-- SoftLayer/transports/fixture.py | 3 +- SoftLayer/transports/rest.py | 3 +- SoftLayer/transports/soap.py | 99 ++++++++++++----------------- SoftLayer/transports/timing.py | 3 +- SoftLayer/transports/xmlrpc.py | 2 - tests/transport_tests.py | 8 +-- tests/transports/debug_tests.py | 8 +-- tests/transports/rest_tests.py | 6 +- tests/transports/soap_tests.py | 19 +----- tests/transports/transport_tests.py | 10 --- tests/transports/xmlrpc_tests.py | 1 - tools/test-requirements.txt | 2 +- 13 files changed, 58 insertions(+), 119 deletions(-) diff --git a/SoftLayer/transports/__init__.py b/SoftLayer/transports/__init__.py index 454f32997..c254a780c 100644 --- a/SoftLayer/transports/__init__.py +++ b/SoftLayer/transports/__init__.py @@ -5,21 +5,16 @@ :license: MIT, see LICENSE for more details. """ +# Required imports to not break existing code. -import requests - - -# Required imports to not break existing code. -from .rest import RestTransport -from .xmlrpc import XmlRpcTransport +from .debug import DebugTransport from .fixture import FixtureTransport +from .rest import RestTransport from .timing import TimingTransport -from .debug import DebugTransport - from .transport import Request from .transport import SoftLayerListResult as SoftLayerListResult - +from .xmlrpc import XmlRpcTransport # transports.Request does have a lot of instance attributes. :( # pylint: disable=too-many-instance-attributes, no-self-use diff --git a/SoftLayer/transports/fixture.py b/SoftLayer/transports/fixture.py index 2fa016665..d94fdabe9 100644 --- a/SoftLayer/transports/fixture.py +++ b/SoftLayer/transports/fixture.py @@ -26,6 +26,7 @@ def __call__(self, call): message = '{}::{} fixture is not implemented'.format(call.service, call.method) raise NotImplementedError(message) from ex - def print_reproduceable(self, call): + @staticmethod + def print_reproduceable(call): """Not Implemented""" return call.service diff --git a/SoftLayer/transports/rest.py b/SoftLayer/transports/rest.py index 3af9c1e40..53d360b1e 100644 --- a/SoftLayer/transports/rest.py +++ b/SoftLayer/transports/rest.py @@ -157,7 +157,8 @@ def __call__(self, request): except requests.RequestException as ex: raise exceptions.TransportError(0, str(ex)) - def print_reproduceable(self, request): + @staticmethod + def print_reproduceable(request): """Prints out the minimal python code to reproduce a specific request The will also automatically replace the API key so its not accidently exposed. diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 585e4e12d..6422b671e 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -6,31 +6,22 @@ :license: MIT, see LICENSE for more details. """ import logging -import re -from string import Template -from zeep import Client, Settings, Transport, xsd -from zeep.helpers import serialize_object +from zeep import Client +from zeep import Settings +from zeep import Transport +from zeep import xsd + from zeep.cache import SqliteCache +from zeep.helpers import serialize_object from zeep.plugins import HistoryPlugin -from zeep.wsdl.messages.multiref import process_multiref - - -import requests from SoftLayer import consts from SoftLayer import exceptions -from .transport import _format_object_mask -from .transport import _proxies_dict -from .transport import ComplexEncoder -from .transport import get_session -from .transport import SoftLayerListResult - -from pprint import pprint as pp - +# pylint: disable=too-many-instance-attributes class SoapTransport(object): - """XML-RPC transport.""" + """SoapTransport.""" def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, verify=True): @@ -44,7 +35,7 @@ def __init__(self, endpoint_url=None, timeout=None, proxy=None, user_agent=None, self.verify = verify self._client = None self.history = HistoryPlugin() - self.soapNS = "http://api.service.softlayer.com/soap/v3.1/" + self.soapns = "http://api.service.softlayer.com/soap/v3.1/" def __call__(self, request): """Makes a SoftLayer API call against the SOAP endpoint. @@ -59,31 +50,31 @@ def __call__(self, request): # print(client.wsdl.dump()) # print("=============== WSDL ==============") - # MUST define headers like this because otherwise the objectMask header doesn't work + + # Must define headers like this because otherwise the objectMask header doesn't work # because it isn't sent in with a namespace. - xsdUserAuth = xsd.Element( - f"{{{self.soapNS}}}authenticate", + xsd_userauth = xsd.Element( + f"{{{self.soapns}}}authenticate", xsd.ComplexType([ - xsd.Element(f'{{{self.soapNS}}}username', xsd.String()), - xsd.Element(f'{{{self.soapNS}}}apiKey', xsd.String()) + xsd.Element(f'{{{self.soapns}}}username', xsd.String()), + xsd.Element(f'{{{self.soapns}}}apiKey', xsd.String()) ]) ) - factory = client.type_factory(f"{self.soapNS}") - theMask = client.get_type(f"{{{self.soapNS}}}SoftLayer_ObjectMask") - xsdMask = xsd.Element( - f"{{{self.soapNS}}}SoftLayer_ObjectMask", - factory['SoftLayer_ObjectMask'] + # factory = client.type_factory(f"{self.soapns}") + the_mask = client.get_type(f"{{{self.soapns}}}SoftLayer_ObjectMask") + xsd_mask = xsd.Element( + f"{{{self.soapns}}}SoftLayer_ObjectMask", the_mask ) # Object Filter - filterType = client.get_type(f"{{{self.soapNS}}}{request.service}ObjectFilter") - xsdFilter = xsd.Element( - f"{{{self.soapNS}}}{request.service}ObjectFilter", filterType + filter_type = client.get_type(f"{{{self.soapns}}}{request.service}ObjectFilter") + xsd_filter = xsd.Element( + f"{{{self.soapns}}}{request.service}ObjectFilter", filter_type ) # Result Limit - xsdResultLimit = xsd.Element( - f"{{{self.soapNS}}}resultLimit", + xsd_resultlimit = xsd.Element( + f"{{{self.soapns}}}resultLimit", xsd.ComplexType([ xsd.Element('limit', xsd.String()), xsd.Element('offset', xsd.String()), @@ -92,54 +83,47 @@ def __call__(self, request): # Might one day want to support unauthenticated requests, but for now assume user auth. headers = [ - xsdUserAuth(username=request.transport_user, apiKey=request.transport_password), + xsd_userauth(username=request.transport_user, apiKey=request.transport_password), ] if request.limit: - headers.append(xsdResultLimit(limit=request.limit, offset=request.offset)) + headers.append(xsd_resultlimit(limit=request.limit, offset=request.offset)) if request.mask: - headers.append(xsdMask(mask=request.mask)) + headers.append(xsd_mask(mask=request.mask)) if request.filter: # The ** here forces python to treat this dict as properties - headers.append(xsdFilter(**request.filter)) + headers.append(xsd_filter(**request.filter)) if request.identifier: - initParam = f"{request.service}InitParameters" - initParamType = client.get_type(f"{{{self.soapNS}}}{initParam}") - xsdInitParam = xsd.Element( - f"{{{self.soapNS}}}{initParam}", initParamType + init_param = f"{request.service}init_parameters" + init_paramtype = client.get_type(f"{{{self.soapns}}}{init_param}") + xsdinit_param = xsd.Element( + f"{{{self.soapns}}}{init_param}", init_paramtype ) # Might want to check if its an id or globalIdentifier at some point, for now only id. - headers.append(xsdInitParam(id=request.identifier)) + headers.append(xsdinit_param(id=request.identifier)) - # TODO Add params... maybe + # NEXT Add params... maybe try: method = getattr(client.service, request.method) except AttributeError as ex: - message = f"{request.service}::{request.method}() does not exist in {self.soapNS}{request.service}?wsdl" + message = f"{request.service}::{request.method}() does not exist in {self.soapns}{request.service}?wsdl" raise exceptions.TransportError(404, message) from ex result = method(_soapheaders=headers) - # result = client.service.getObject(_soapheaders=headers) - - # process_multiref(result['body']['getAllObjectsReturn']) - # print("^^^ RESULT ^^^^^^^") - - # TODO GET A WAY TO FIND TOTAL ITEMS - # print(result['header']['totalItems']['amount']) - # print(" ^^ ITEMS ^^^ ") + # NEXT GET A WAY TO FIND TOTAL ITEMS try: - methodReturn = f"{request.method}Return" + method_return = f"{request.method}Return" serialize = serialize_object(result) if serialize.get('body'): - return serialize['body'][methodReturn] + return serialize['body'][method_return] else: # Some responses (like SoftLayer_Account::getObject) don't have a body? return serialize - except KeyError as e: - message = f"Error serializeing response\n{result}\n" + except KeyError as ex: + message = f"Error serializeing response\n{result}\n{ex}" raise exceptions.TransportError(500, message) def print_reproduceable(self, request): @@ -149,5 +133,6 @@ def print_reproduceable(self, request): :param request request: Request object """ - + log = logging.getLogger(__name__) + log.DEBUG(f"{request.service}::{request.method}()") return self.history.last_sent diff --git a/SoftLayer/transports/timing.py b/SoftLayer/transports/timing.py index 5b9345276..d32020dfc 100644 --- a/SoftLayer/transports/timing.py +++ b/SoftLayer/transports/timing.py @@ -35,6 +35,7 @@ def get_last_calls(self): self.last_calls = [] return last_calls - def print_reproduceable(self, call): + @staticmethod + def print_reproduceable(call): """Not Implemented""" return call.service diff --git a/SoftLayer/transports/xmlrpc.py b/SoftLayer/transports/xmlrpc.py index c10481812..4830e376b 100644 --- a/SoftLayer/transports/xmlrpc.py +++ b/SoftLayer/transports/xmlrpc.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -import logging import re from string import Template import xmlrpc.client @@ -17,7 +16,6 @@ from .transport import _format_object_mask from .transport import _proxies_dict -from .transport import ComplexEncoder from .transport import get_session from .transport import SoftLayerListResult diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 0c175df5c..2c4a6bfb6 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -4,16 +4,10 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest import requests +from unittest import mock as mock import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py index 2527cb302..7fe9621c0 100644 --- a/tests/transports/debug_tests.py +++ b/tests/transports/debug_tests.py @@ -4,16 +4,10 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest import requests +from unittest import mock as mock import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index f0c69cea0..e8825c3f3 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -4,16 +4,12 @@ :license: MIT, see LICENSE for more details. """ -import io import json +import requests from unittest import mock as mock import warnings -import pytest -import requests - import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index d4b0bf335..50a8c7b37 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -5,22 +5,12 @@ :license: MIT, see LICENSE for more details. """ import io -import json -from unittest import mock as mock import os -import warnings - -import pytest import requests -import SoftLayer -from SoftLayer import consts from SoftLayer import testing -from SoftLayer.transports.soap import SoapTransport from SoftLayer.transports import Request - - -from pprint import pprint as pp +from SoftLayer.transports.soap import SoapTransport def get_soap_response(): @@ -58,7 +48,6 @@ def set_up(self): def test_call(self): data = self.transport(self.request) - pp(data) self.assertEqual(data.get('id'), 307608) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") @@ -75,7 +64,6 @@ def test_call(self): def test_objectMask(self): self.request.mask = "mask[id,companyName]" data = self.transport(self.request) - pp(data) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) @@ -88,10 +76,7 @@ def test_objectFilter(self): self.request.limit = 5 self.request.offset = 0 data = self.transport(self.request) - # pp(data) - # print("^^^ DATA **** ") for package in data: - self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") def test_virtualGuest(self): @@ -117,4 +102,4 @@ def test_virtualGuest(self): thisVsi = self.transport(vsiRequest) self.assertEqual(thisVsi.get('id'), vsi.get('id')) - # TODO MORE COMPLEX OBJECT FILTERS! + # NEXT MORE COMPLEX OBJECT FILTERS! diff --git a/tests/transports/transport_tests.py b/tests/transports/transport_tests.py index 32b1eaad9..c22d11b9d 100644 --- a/tests/transports/transport_tests.py +++ b/tests/transports/transport_tests.py @@ -4,16 +4,6 @@ :license: MIT, see LICENSE for more details. """ -import io -import json -from unittest import mock as mock -import warnings - -import pytest -import requests - -import SoftLayer -from SoftLayer import consts from SoftLayer import testing from SoftLayer import transports diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index 8852a327d..4797c3dc8 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ import io -import json from unittest import mock as mock import warnings diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index ab52a13fa..b3c013b51 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,4 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep \ No newline at end of file +zeep == 4.1.0 \ No newline at end of file From 7813c7939bd326a8cce2a1c74477028920cda9ed Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 26 Apr 2022 17:23:20 -0400 Subject: [PATCH 0307/1050] Unable to get VSI details when last TXN is "Software install is finished" --- SoftLayer/CLI/virt/detail.py | 8 +++++--- tests/CLI/modules/vs/vs_tests.py | 10 ++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index ac9453ddd..30ec44f52 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,11 +69,13 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), - utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + last_transaction = '' + if result.get('lastTransaction') != []: + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) table.add_row(['last_transaction', last_transaction]) - table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 99fafc0bf..f56212561 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -947,3 +947,13 @@ def test_authorize_volume_and_portable_storage_vs(self): def test_monitoring_vs(self): result = self.run_command(['vs', 'monitoring', '1234']) self.assert_no_fail(result) + + def test_last_transaction_empty(self): + vg_return = SoftLayer_Virtual_Guest.getObject + transaction = [] + + vg_return['lastTransaction'] = transaction + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.return_value = vg_return + result = self.run_command(['vs', 'detail', '100']) + self.assert_no_fail(result) From 1ccbb072adf55f54664f638c5f419b1178d20281 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 27 Apr 2022 12:15:37 -0400 Subject: [PATCH 0308/1050] fix the team code review comments --- SoftLayer/CLI/autoscale/create.py | 21 ++++----------------- SoftLayer/managers/network.py | 2 -- SoftLayer/utils.py | 5 +++++ 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py index 86f17ddb8..1a0dde07a 100644 --- a/SoftLayer/CLI/autoscale/create.py +++ b/SoftLayer/CLI/autoscale/create.py @@ -1,7 +1,8 @@ -"""Order/create a dedicated server.""" +"""Order/Create a scale group.""" # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils import SoftLayer from SoftLayer.CLI import environment @@ -36,13 +37,10 @@ @helpers.multi_option('--disk', help="Disk sizes") @environment.pass_env def cli(env, **args): - """Order/create autoscale.""" + """Order/Create a scale group.""" scale = AutoScaleManager(env.client) network = SoftLayer.NetworkManager(env.client) - pods = network.get_closed_pods() - closure = [] - datacenter = network.get_datacenter(args.get('datacenter')) ssh_keys = [] @@ -102,16 +100,10 @@ def cli(env, **args): 'balancedTerminationFlag': False, 'virtualGuestMemberTemplate': virt_template, 'virtualGuestMemberCount': 0, - 'policies': policies.append(clean_dict(policy_template)), + 'policies': policies.append(utils.clean_dict(policy_template)), 'terminationPolicyId': args['termination_policy'] } - # print(virt_template) - - for pod in pods: - if args.get('datacenter') in str(pod['name']): - closure.append(pod['name']) - click.secho(click.style('Warning: Closed soon: %s' % (', '.join(closure)), fg='yellow')) if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting scale group order.') @@ -130,8 +122,3 @@ def cli(env, **args): output = table env.fout(output) - - -def clean_dict(dictionary): - """Removes any `None` entires from the dictionary""" - return {k: v for k, v in dictionary.items() if v} diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 0128abb51..d37f778e3 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -830,8 +830,6 @@ def get_datacenter(self, _filter=None, datacenter=None): returns datacenter list. """ - _filter = None - if datacenter: _filter = {"name": {"operation": datacenter}} diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 05ef50471..d34befb8a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -427,3 +427,8 @@ def format_comment(comment, max_line_length=60): comment_length = len(word) + 1 return formatted_comment + + +def clean_dict(dictionary): + """Removes any `None` entires from the dictionary""" + return {k: v for k, v in dictionary.items() if v} From 87f84d0e3892bb6e670ac25991f81b3506518ad9 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 27 Apr 2022 16:13:30 -0500 Subject: [PATCH 0309/1050] improved soap unit tests --- .../soap/SoftLayer_Account_getObject.soap | 11 ++++ .../SoftLayer_Account_getVirtualGuests.soap | 19 +++++++ .../SoftLayer_Virtual_Guest_getObject.soap | 12 ++++ .../fixtures/soap/test_objectFilter.soap | 2 + SoftLayer/fixtures/soap/test_objectMask.soap | 2 + SoftLayer/transports/soap.py | 2 +- tests/transports/soap_tests.py | 55 +++++++++++-------- tests/transports/xmlrpc_tests.py | 30 +++++----- tools/requirements.txt | 3 +- tools/test-requirements.txt | 2 +- 10 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap create mode 100644 SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap create mode 100644 SoftLayer/fixtures/soap/test_objectFilter.soap create mode 100644 SoftLayer/fixtures/soap/test_objectMask.soap diff --git a/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap b/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap new file mode 100644 index 000000000..3cec2abfc --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Account_getObject.soap @@ -0,0 +1,11 @@ + + + + + + SoftLayer Internal - Development Community + 307608 + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap b/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap new file mode 100644 index 000000000..7648b1aac --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Account_getVirtualGuests.soap @@ -0,0 +1,19 @@ + + + + + 1 + + + + + + + cgallo.com + KVM-Test + 121401696 + + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap b/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap new file mode 100644 index 000000000..ad3668c63 --- /dev/null +++ b/SoftLayer/fixtures/soap/SoftLayer_Virtual_Guest_getObject.soap @@ -0,0 +1,12 @@ + + + + + + ibm.com + KVM-Test + 121401696 + + + + \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/test_objectFilter.soap b/SoftLayer/fixtures/soap/test_objectFilter.soap new file mode 100644 index 000000000..70847b8af --- /dev/null +++ b/SoftLayer/fixtures/soap/test_objectFilter.soap @@ -0,0 +1,2 @@ + +109562U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EXQuad Processor Multi Core Nehalem EX2BARE_METAL_CPU126SINGLE_XEON_1200_SANDY_BRIDGE_HASWELLSingle Xeon 1200 Series (Sandy Bridge / Haswell)142SINGLE_XEON_2000_SANDY_BRIDGESingle Xeon 2000 Series (Sandy Bridge)143DUAL_XEON_2000_SANDY_BRIDGEDual Xeon 2000 Series (Sandy Bridge)1443U_GPUSpecialty Server: GPU \ No newline at end of file diff --git a/SoftLayer/fixtures/soap/test_objectMask.soap b/SoftLayer/fixtures/soap/test_objectMask.soap new file mode 100644 index 000000000..85ea89eb5 --- /dev/null +++ b/SoftLayer/fixtures/soap/test_objectMask.soap @@ -0,0 +1,2 @@ + +SoftLayer Internal - Development Community307608 \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 6422b671e..19afab052 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -95,7 +95,7 @@ def __call__(self, request): headers.append(xsd_filter(**request.filter)) if request.identifier: - init_param = f"{request.service}init_parameters" + init_param = f"{request.service}InitParameters" init_paramtype = client.get_type(f"{{{self.soapns}}}{init_param}") xsdinit_param = xsd.Element( f"{{{self.soapns}}}{init_param}", init_paramtype diff --git a/tests/transports/soap_tests.py b/tests/transports/soap_tests.py index 50a8c7b37..e7ac4f611 100644 --- a/tests/transports/soap_tests.py +++ b/tests/transports/soap_tests.py @@ -1,5 +1,5 @@ """ - SoftLayer.tests.transports.xmlrc + SoftLayer.tests.transports.soap ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. @@ -7,27 +7,24 @@ import io import os import requests +from unittest import mock as mock from SoftLayer import testing from SoftLayer.transports import Request from SoftLayer.transports.soap import SoapTransport -def get_soap_response(): +def setup_response(filename, status_code=200, total_items=1): + basepath = os.path.dirname(__file__) + body = b'' + print(f"Base Path: {basepath}") + with open(f"{basepath}/../../SoftLayer/fixtures/soap/{filename}.soap", 'rb') as fixture: + body = fixture.read() response = requests.Response() - list_body = b''' - - - - - - - - -''' + list_body = body response.raw = io.BytesIO(list_body) - response.headers['SoftLayer-Total-Items'] = 10 - response.status_code = 200 + response.headers['SoftLayer-Total-Items'] = total_items + response.status_code = status_code return response @@ -35,9 +32,11 @@ class TestSoapAPICall(testing.TestCase): def set_up(self): self.transport = SoapTransport(endpoint_url='https://api.softlayer.com/soap/v3.1/') - self.response = get_soap_response() - self.user = os.getenv('SL_USER') - self.password = os.environ.get('SL_APIKEY') + + self.user = "testUser" + self.password = "testPassword" + # self.user = os.getenv('SL_USER') + # self.password = os.environ.get('SL_APIKEY') request = Request() request.service = 'SoftLayer_Account' request.method = 'getObject' @@ -45,8 +44,10 @@ def set_up(self): request.transport_password = self.password self.request = request - def test_call(self): - + @mock.patch('requests.Session.post') + def test_call(self, zeep_post): + zeep_post.return_value = setup_response('SoftLayer_Account_getObject') + self.request.mask = "mask[id,companyName]" data = self.transport(self.request) self.assertEqual(data.get('id'), 307608) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") @@ -61,14 +62,18 @@ def test_call(self): # print(debug_data['envelope']) # self.assertEqual(":sdfsdf", debug_data) - def test_objectMask(self): + @mock.patch('requests.Session.post') + def test_objectMask(self, zeep_post): + zeep_post.return_value = setup_response('test_objectMask') self.request.mask = "mask[id,companyName]" data = self.transport(self.request) self.assertEqual(data.get('companyName'), "SoftLayer Internal - Development Community") self.assertIsNone(data.get('address1')) self.assertEqual(data.get('id'), 307608) - def test_objectFilter(self): + @mock.patch('requests.Session.post') + def test_objectFilter(self, zeep_post): + zeep_post.return_value = setup_response('test_objectFilter') self.request.service = "SoftLayer_Product_Package" self.request.method = "getAllObjects" self.request.mask = "mask[id,description,keyName,type[id,keyName],name]" @@ -79,7 +84,12 @@ def test_objectFilter(self): for package in data: self.assertEqual(package.get('type').get('keyName'), "BARE_METAL_CPU") - def test_virtualGuest(self): + @mock.patch('requests.Session.post') + def test_virtualGuest(self, zeep_post): + zeep_post.side_effect = [ + setup_response('SoftLayer_Account_getVirtualGuests'), + setup_response('SoftLayer_Virtual_Guest_getObject') + ] accountRequest = Request() accountRequest.service = "SoftLayer_Account" accountRequest.method = "getVirtualGuests" @@ -90,6 +100,7 @@ def test_virtualGuest(self): accountRequest.transport_password = self.password vsis = self.transport(accountRequest) + self.assertEqual(1, len(vsis)) for vsi in vsis: self.assertGreater(vsi.get('id'), 1) vsiRequest = Request() diff --git a/tests/transports/xmlrpc_tests.py b/tests/transports/xmlrpc_tests.py index 4797c3dc8..6e669279e 100644 --- a/tests/transports/xmlrpc_tests.py +++ b/tests/transports/xmlrpc_tests.py @@ -43,7 +43,7 @@ def set_up(self): ) self.response = get_xmlrpc_response() - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_call(self, request): request.return_value = self.response @@ -95,7 +95,7 @@ def test_proxy_without_protocol(self): warnings.warn("Incorrect Exception raised. Expected a " "SoftLayer.TransportError error") - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_valid_proxy(self, request): request.return_value = self.response self.transport.proxy = 'http://localhost:3128' @@ -117,7 +117,7 @@ def test_valid_proxy(self, request): verify=True, auth=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_identifier(self, request): request.return_value = self.response @@ -135,7 +135,7 @@ def test_identifier(self, request): 1234 """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_filter(self, request): request.return_value = self.response @@ -153,7 +153,7 @@ def test_filter(self, request): ^= prefix """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_limit_offset(self, request): request.return_value = self.response @@ -173,7 +173,7 @@ def test_limit_offset(self, request): 10 """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_old_mask(self, request): request.return_value = self.response @@ -195,7 +195,7 @@ def test_old_mask(self, request): """.encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_no_mask_prefix(self, request): request.return_value = self.response @@ -211,7 +211,7 @@ def test_mask_call_no_mask_prefix(self, request): "mask[something.nested]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_v2(self, request): request.return_value = self.response @@ -227,7 +227,7 @@ def test_mask_call_v2(self, request): "mask[something[nested]]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_filteredMask(self, request): request.return_value = self.response @@ -243,7 +243,7 @@ def test_mask_call_filteredMask(self, request): "filteredMask[something[nested]]".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_mask_call_v2_dot(self, request): request.return_value = self.response @@ -258,7 +258,7 @@ def test_mask_call_v2_dot(self, request): self.assertIn("mask.something.nested".encode(), kwargs['data']) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_request_exception(self, request): # Test Text Error e = requests.HTTPError('error') @@ -281,7 +281,7 @@ def test_print_reproduceable(self): output_text = self.transport.print_reproduceable(req) self.assertIn("https://test.com", output_text) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') @mock.patch('requests.auth.HTTPBasicAuth') def test_ibm_id_call(self, auth, request): request.return_value = self.response @@ -325,7 +325,7 @@ def test_ibm_id_call(self, auth, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_call_large_number_response(self, request): response = requests.Response() body = b''' @@ -359,7 +359,7 @@ def test_call_large_number_response(self, request): resp = self.transport(req) self.assertEqual(resp[0]['bytesUsed'], 2666148982056) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') def test_nonascii_characters(self, request): request.return_value = self.response hostname = 'testé' @@ -411,7 +411,7 @@ def test_nonascii_characters(self, request): self.assertEqual(resp.total_count, 10) -@mock.patch('SoftLayer.transports.requests.Session.request') +@mock.patch('SoftLayer.transports.xmlrpc.requests.Session.request') @pytest.mark.parametrize( "transport_verify,request_verify,expected", [ diff --git a/tools/requirements.txt b/tools/requirements.txt index 880148ffd..dc49002d7 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -4,4 +4,5 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep +# only used for soap transport +# softlayer-zeep >= 5.0.0 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index b3c013b51..4c79f53ac 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -10,4 +10,4 @@ requests >= 2.20.0 prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 -zeep == 4.1.0 \ No newline at end of file +softlayer-zeep >= 5.0.0 \ No newline at end of file From d885ce5b71a62961d9cc216ba58069b17dbab535 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 27 Apr 2022 18:58:23 -0400 Subject: [PATCH 0310/1050] fix the tox tool --- SoftLayer/CLI/autoscale/create.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/autoscale/create.py b/SoftLayer/CLI/autoscale/create.py index 1a0dde07a..74ba9ba65 100644 --- a/SoftLayer/CLI/autoscale/create.py +++ b/SoftLayer/CLI/autoscale/create.py @@ -107,18 +107,18 @@ def cli(env, **args): if not (env.skip_confirmations or formatting.confirm( "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting scale group order.') - else: - result = scale.create(order) - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Id', result['id']]) - table.add_row(['Created', result['createDate']]) - table.add_row(['Name', result['name']]) - table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) - table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) - table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) - output = table + result = scale.create(order) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', result['id']]) + table.add_row(['Created', result['createDate']]) + table.add_row(['Name', result['name']]) + table.add_row(['Virtual Guest Id', result['virtualGuestMembers'][0]['virtualGuest']['id']]) + table.add_row(['Virtual Guest domain', result['virtualGuestMembers'][0]['virtualGuest']['domain']]) + table.add_row(['Virtual Guest hostname', result['virtualGuestMembers'][0]['virtualGuest']['hostname']]) + output = table env.fout(output) From fbed4c2f0970cbe20c039b3a4a53f91e81a9d9ce Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Apr 2022 08:32:28 -0400 Subject: [PATCH 0311/1050] fix team code review comments --- SoftLayer/CLI/virt/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 30ec44f52..94b37f742 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -70,7 +70,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) last_transaction = '' - if result.get('lastTransaction') != []: + if result.get('lastTransaction'): last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) From e0f7fc3d6f6ceb9ebb252c30ec35884e0cea5383 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Apr 2022 09:34:56 -0500 Subject: [PATCH 0312/1050] fixed unit tests for SOAP transport --- .../fixtures/soap/test_objectFilter.soap | 47 ++++++- SoftLayer/transports/soap.py | 2 + setup.py | 3 +- tests/transport_tests.py | 133 ------------------ tests/transports/debug_tests.py | 2 +- tests/transports/rest_tests.py | 28 ++-- 6 files changed, 64 insertions(+), 151 deletions(-) delete mode 100644 tests/transport_tests.py diff --git a/SoftLayer/fixtures/soap/test_objectFilter.soap b/SoftLayer/fixtures/soap/test_objectFilter.soap index 70847b8af..70da7e976 100644 --- a/SoftLayer/fixtures/soap/test_objectFilter.soap +++ b/SoftLayer/fixtures/soap/test_objectFilter.soap @@ -1,2 +1,47 @@ -109562U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EXQuad Processor Multi Core Nehalem EX2BARE_METAL_CPU126SINGLE_XEON_1200_SANDY_BRIDGE_HASWELLSingle Xeon 1200 Series (Sandy Bridge / Haswell)142SINGLE_XEON_2000_SANDY_BRIDGESingle Xeon 2000 Series (Sandy Bridge)143DUAL_XEON_2000_SANDY_BRIDGEDual Xeon 2000 Series (Sandy Bridge)1443U_GPUSpecialty Server: GPU \ No newline at end of file + + + + 109 + + + + + + + 56 + 2U_QUAD_PROCESSOR_MULTI_CORE_NEHALEM_EX + Quad Processor Multi Core Nehalem EX + + 2 + BARE_METAL_CPU + + + + 126 + SINGLE_XEON_1200_SANDY_BRIDGE_HASWELL + Single Xeon 1200 Series (Sandy Bridge / Haswell) + + + + 142 + SINGLE_XEON_2000_SANDY_BRIDGE + Single Xeon 2000 Series (Sandy Bridge) + + + + 143 + DUAL_XEON_2000_SANDY_BRIDGE + Dual Xeon 2000 Series (Sandy Bridge) + + + + 144 + 3U_GPU + Specialty Server: GPU + + + + + + \ No newline at end of file diff --git a/SoftLayer/transports/soap.py b/SoftLayer/transports/soap.py index 19afab052..d604ec705 100644 --- a/SoftLayer/transports/soap.py +++ b/SoftLayer/transports/soap.py @@ -6,6 +6,8 @@ :license: MIT, see LICENSE for more details. """ import logging + +# Try to import zeep, make sure its softlayer_zeep, error otherwise from zeep import Client from zeep import Settings from zeep import Transport diff --git a/setup.py b/setup.py index 97514d838..09921feaf 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,7 @@ 'requests >= 2.20.0', 'prompt_toolkit >= 2', 'pygments >= 2.0.0', - 'urllib3 >= 1.24', - 'zeep' + 'urllib3 >= 1.24' ], keywords=['softlayer', 'cloud', 'slcli'], classifiers=[ diff --git a/tests/transport_tests.py b/tests/transport_tests.py deleted file mode 100644 index 2c4a6bfb6..000000000 --- a/tests/transport_tests.py +++ /dev/null @@ -1,133 +0,0 @@ -""" - SoftLayer.tests.transport_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -import requests -from unittest import mock as mock - -import SoftLayer -from SoftLayer import testing -from SoftLayer import transports - - -class TestFixtureTransport(testing.TestCase): - - def set_up(self): - self.transport = transports.FixtureTransport() - - def test_basic(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - - def test_no_module(self): - req = transports.Request() - req.service = 'Doesnt_Exist' - req.method = 'getObject' - self.assertRaises(NotImplementedError, self.transport, req) - - def test_no_method(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObjectzzzz' - self.assertRaises(NotImplementedError, self.transport, req) - - -class TestTimingTransport(testing.TestCase): - - def set_up(self): - fixture_transport = transports.FixtureTransport() - self.transport = transports.TimingTransport(fixture_transport) - - def test_call(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - - def test_get_last_calls(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - resp = self.transport(req) - self.assertEqual(resp['accountId'], 1234) - calls = self.transport.get_last_calls() - self.assertEqual(calls[0][0].service, 'SoftLayer_Account') - - def test_print_reproduceable(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - output_text = self.transport.print_reproduceable(req) - self.assertEqual('SoftLayer_Account', output_text) - - -class TestDebugTransport(testing.TestCase): - - def set_up(self): - fixture_transport = transports.FixtureTransport() - self.transport = transports.DebugTransport(fixture_transport) - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - self.req = req - - def test_call(self): - - resp = self.transport(self.req) - self.assertEqual(resp['accountId'], 1234) - - def test_get_last_calls(self): - - resp = self.transport(self.req) - self.assertEqual(resp['accountId'], 1234) - calls = self.transport.get_last_calls() - self.assertEqual(calls[0].service, 'SoftLayer_Account') - - def test_print_reproduceable(self): - req = transports.Request() - req.service = 'SoftLayer_Account' - req.method = 'getObject' - output_text = self.transport.print_reproduceable(self.req) - self.assertEqual('SoftLayer_Account', output_text) - - def test_print_reproduceable_post(self): - req = transports.Request() - req.url = "https://test.com" - req.payload = "testing" - req.transport_headers = {"test-headers": 'aaaa'} - req.args = 'createObject' - - rest_transport = transports.RestTransport() - transport = transports.DebugTransport(rest_transport) - - output_text = transport.print_reproduceable(req) - - self.assertIn("https://test.com", output_text) - self.assertIn("-X POST", output_text) - - @mock.patch('SoftLayer.transports.requests.Session.request') - def test_error(self, request): - # Test JSON Error - e = requests.HTTPError('error') - e.response = mock.MagicMock() - e.response.status_code = 404 - e.response.text = '''{ - "error": "description", - "code": "Error Code" - }''' - request().raise_for_status.side_effect = e - - req = transports.Request() - req.service = 'SoftLayer_Service' - req.method = 'Resource' - rest_transport = transports.RestTransport() - transport = transports.DebugTransport(rest_transport) - self.assertRaises(SoftLayer.SoftLayerAPIError, transport, req) - calls = transport.get_last_calls() - self.assertEqual(404, calls[0].exception.faultCode) diff --git a/tests/transports/debug_tests.py b/tests/transports/debug_tests.py index 7fe9621c0..a95f32edd 100644 --- a/tests/transports/debug_tests.py +++ b/tests/transports/debug_tests.py @@ -56,7 +56,7 @@ def test_print_reproduceable_post(self): self.assertIn("https://test.com", output_text) self.assertIn("-X POST", output_text) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_error(self, request): # Test JSON Error e = requests.HTTPError('error') diff --git a/tests/transports/rest_tests.py b/tests/transports/rest_tests.py index e8825c3f3..2c3d5f68f 100644 --- a/tests/transports/rest_tests.py +++ b/tests/transports/rest_tests.py @@ -21,7 +21,7 @@ def set_up(self): endpoint_url='http://something9999999999999999999999.com', ) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_basic(self, request): request().content = '[]' request().text = '[]' @@ -48,7 +48,7 @@ def test_basic(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_http_and_json_error(self, request): # Test JSON Error e = requests.HTTPError('error') @@ -65,7 +65,7 @@ def test_http_and_json_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_http_and_empty_error(self, request): # Test JSON Error e = requests.HTTPError('error') @@ -79,7 +79,7 @@ def test_http_and_empty_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_empty_error(self, request): # Test empty response error. request().text = '' @@ -89,7 +89,7 @@ def test_empty_error(self, request): req.method = 'Resource' self.assertRaises(SoftLayer.SoftLayerAPIError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_json_error(self, request): # Test non-json response error. request().text = 'Not JSON' @@ -109,7 +109,7 @@ def test_proxy_without_protocol(self): except AssertionError: warnings.warn("AssertionError raised instead of a SoftLayer.TransportError error") - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_valid_proxy(self, request): request().text = '{}' self.transport.proxy = 'http://localhost:3128' @@ -132,7 +132,7 @@ def test_valid_proxy(self, request): timeout=mock.ANY, headers=mock.ANY) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_id(self, request): request().text = '{}' @@ -156,7 +156,7 @@ def test_with_id(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_args(self, request): request().text = '{}' @@ -180,7 +180,7 @@ def test_with_args(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_args_bytes(self, request): request().text = '{}' @@ -204,7 +204,7 @@ def test_with_args_bytes(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_filter(self, request): request().text = '{}' @@ -229,7 +229,7 @@ def test_with_filter(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_mask(self, request): request().text = '{}' @@ -274,7 +274,7 @@ def test_with_mask(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_with_limit_offset(self, request): request().text = '{}' @@ -300,7 +300,7 @@ def test_with_limit_offset(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') def test_unknown_error(self, request): e = requests.RequestException('error') e.response = mock.MagicMock() @@ -314,7 +314,7 @@ def test_unknown_error(self, request): self.assertRaises(SoftLayer.TransportError, self.transport, req) - @mock.patch('SoftLayer.transports.requests.Session.request') + @mock.patch('SoftLayer.transports.rest.requests.Session.request') @mock.patch('requests.auth.HTTPBasicAuth') def test_with_special_auth(self, auth, request): request().text = '{}' From 2bb1d332bd4d7ce8d880239bc97b002c141bca83 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 28 Apr 2022 18:20:42 -0400 Subject: [PATCH 0313/1050] new command on autoscale delete --- SoftLayer/CLI/autoscale/delete.py | 28 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 3 ++- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 1 + SoftLayer/managers/autoscale.py | 10 ++++++++ docs/cli/autoscale.rst | 4 +++ tests/CLI/modules/autoscale_tests.py | 4 +++ tests/managers/autoscale_tests.py | 4 +++ 7 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/autoscale/delete.py diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py new file mode 100644 index 000000000..6b790758f --- /dev/null +++ b/SoftLayer/CLI/autoscale/delete.py @@ -0,0 +1,28 @@ +"""Delete autoscale.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.autoscale import AutoScaleManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Sets a user's status to CANCEL_PENDING, which will immediately disable the account, + + and will eventually be fully removed from the account by an automated internal process. + + Example: slcli user delete userId + + """ + + autoscale = AutoScaleManager(env.client) + result = autoscale.delete(identifier) + + if result: + click.secho("%s deleted successfully" % identifier, fg='green') + else: + click.secho("Failed to delete %s" % identifier, fg='red') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index ac2d5084d..a4cdff5cd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -377,7 +377,8 @@ ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), ('autoscale:tag', 'SoftLayer.CLI.autoscale.tag:cli'), - ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli') + ('autoscale:edit', 'SoftLayer.CLI.autoscale.edit:cli'), + ('autoscale:delete', 'SoftLayer.CLI.autoscale.delete:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index f04d8f56e..909ed88af 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -455,3 +455,4 @@ ] editObject = True +forceDeleteObject = True diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 78fa18e31..772aa40fa 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -116,3 +116,13 @@ def edit(self, identifier, template): .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ """ return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) + + def delete(self, identifier): + """Calls `SoftLayer_Scale_Group::forceDeleteObject()`_ + + :param identifier: SoftLayer_Scale_Group id + + .. _SoftLayer_Scale_Group::forceDeleteObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/forceDeleteObject/ + """ + return self.client.call('SoftLayer_Scale_Group', 'forceDeleteObject', id=identifier) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index a3aa31462..f1ebf1462 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -34,5 +34,9 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :prog: autoscale edit :show-nested: +.. click:: SoftLayer.CLI.autoscale.delete:cli + :prog: autoscale delte + :show-nested: + .. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale \ No newline at end of file diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index cb3cdfdb9..5d09209c8 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -89,3 +89,7 @@ def test_autoscale_edit_userfile(self, manager): result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) self.assert_no_fail(result) manager.assert_called_with('12345', template) + + def test_autoscale_delete(self): + result = self.run_command(['autoscale', 'delete', '12345']) + self.assert_no_fail(result) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 6da505409..3d41e6219 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -123,3 +123,7 @@ def test_edit_object(self): 'editObject', args=(template,), identifier=12345) + + def test_delete_object(self): + self.autoscale.delete(12345) + self.assert_called_with('SoftLayer_Scale_Group', 'forceDeleteObject') From 2a753cd5f4a32bf2c701ddefcb7aee74ed67986e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 08:49:09 -0400 Subject: [PATCH 0314/1050] fix the tox analysis --- docs/cli/autoscale.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index f1ebf1462..5e05c4630 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -35,7 +35,7 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :show-nested: .. click:: SoftLayer.CLI.autoscale.delete:cli - :prog: autoscale delte + :prog: autoscale delete :show-nested: From 1bd74f8a597eb2e11280eb008c7da8a8d429f2f0 Mon Sep 17 00:00:00 2001 From: Brian Flores Date: Fri, 29 Apr 2022 16:07:00 -0400 Subject: [PATCH 0315/1050] Fix response table title --- SoftLayer/CLI/autoscale/scale.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py index 69fe1305a..8bc22c279 100644 --- a/SoftLayer/CLI/autoscale/scale.py +++ b/SoftLayer/CLI/autoscale/scale.py @@ -37,14 +37,14 @@ def cli(env, identifier, scale_up, scale_by, amount): try: # Check if the first guest has a cancellation date, assume we are removing guests if it is. - cancel_date = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False + status = result[0]['virtualGuest']['status']['keyName'] or False except (IndexError, KeyError, TypeError): - cancel_date = False + status = False - if cancel_date: - member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") - else: + if status == 'ACTIVE': member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests") + else: + member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") for guest in result: real_guest = guest.get('virtualGuest') From 2d30c20cf278a5322be7a4fe26ba3f87b1584318 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 18:12:44 -0400 Subject: [PATCH 0316/1050] fix the team code review comments --- SoftLayer/CLI/autoscale/delete.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py index 6b790758f..3591a3e33 100644 --- a/SoftLayer/CLI/autoscale/delete.py +++ b/SoftLayer/CLI/autoscale/delete.py @@ -11,11 +11,9 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Sets a user's status to CANCEL_PENDING, which will immediately disable the account, + """Delete this group and destroy all members of it. - and will eventually be fully removed from the account by an automated internal process. - - Example: slcli user delete userId + Example: slcli autoscale delete autoscaleId """ @@ -24,5 +22,3 @@ def cli(env, identifier): if result: click.secho("%s deleted successfully" % identifier, fg='green') - else: - click.secho("Failed to delete %s" % identifier, fg='red') From fc09359c0f701f9f89efe90eec57089b12f440fe Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 29 Apr 2022 18:33:13 -0400 Subject: [PATCH 0317/1050] fix the team code review comments --- SoftLayer/CLI/autoscale/delete.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/autoscale/delete.py b/SoftLayer/CLI/autoscale/delete.py index 3591a3e33..34f41ddc3 100644 --- a/SoftLayer/CLI/autoscale/delete.py +++ b/SoftLayer/CLI/autoscale/delete.py @@ -1,7 +1,7 @@ """Delete autoscale.""" # :license: MIT, see LICENSE for more details. - import click +import SoftLayer from SoftLayer.CLI import environment from SoftLayer.managers.autoscale import AutoScaleManager @@ -18,7 +18,9 @@ def cli(env, identifier): """ autoscale = AutoScaleManager(env.client) - result = autoscale.delete(identifier) - if result: + try: + autoscale.delete(identifier) click.secho("%s deleted successfully" % identifier, fg='green') + except SoftLayer.SoftLayerAPIError as ex: + click.secho("Failed to delete %s\n%s" % (identifier, ex), fg='red') From a72aac4c2f46d817afbfd1f48c0ea3b43b6b3d69 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 3 May 2022 16:35:56 -0500 Subject: [PATCH 0318/1050] some basic textualize support #1630 --- SoftLayer/CLI/core.py | 120 ++++++++++++++++++++++++++++++++++-- setup.py | 7 ++- tools/requirements.txt | 5 +- tools/test-requirements.txt | 5 +- 4 files changed, 124 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 0f7c12f8c..2c303195c 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import inspect import logging import os import sys @@ -15,6 +16,15 @@ import click import requests +from rich.console import Console, RenderableType +from rich.markup import escape +from rich.text import Text +from rich.highlighter import RegexHighlighter +from rich.panel import Panel +from rich.table import Table +from rich.theme import Theme + + import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -40,6 +50,24 @@ DEFAULT_FORMAT = 'table' +class OptionHighlighter(RegexHighlighter): + highlights = [ + r"(?P\-\w)", # single options like -v + r"(?P