From 677f4349abf21b3edd4a6bcd23609b6a0bb0c979 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Mon, 18 Oct 2021 09:51:24 +0530 Subject: [PATCH 001/831] 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 002/831] 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 003/831] 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 004/831] 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 005/831] 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 da4593ec14163a5ec46195b486aa1b78d62e2576 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Sat, 23 Oct 2021 12:33:49 +0530 Subject: [PATCH 006/831] 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 4ccb9b823048d80a3b06124b6a0255f1ae0bf0b0 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 3 Nov 2021 22:00:09 +0530 Subject: [PATCH 007/831] 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 008/831] 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 009/831] 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 010/831] 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 011/831] #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 012/831] 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 013/831] 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 014/831] 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 015/831] 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 016/831] #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 017/831] 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 018/831] 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 019/831] 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 020/831] 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 021/831] 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 022/831] 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 023/831] 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 024/831] 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 025/831] #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 026/831] #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 027/831] 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 028/831] #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 029/831] 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 030/831] 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 031/831] 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 032/831] 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 033/831] 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 034/831] 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 035/831] 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 036/831] 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 037/831] 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 038/831] 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 039/831] #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 040/831] #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 041/831] 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 042/831] 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 043/831] 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 044/831] 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 045/831] 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 046/831] 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 047/831] 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 048/831] 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 049/831] 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 050/831] 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 051/831] 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 052/831] 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 053/831] 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 054/831] 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 055/831] 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 056/831] 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 057/831] 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 058/831] 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 059/831] 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 060/831] 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 061/831] 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 062/831] 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 063/831] 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 064/831] 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 065/831] 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 066/831] 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 067/831] 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 068/831] 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 069/831] #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 070/831] 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 071/831] 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 072/831] 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 073/831] 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 074/831] 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 075/831] 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 076/831] 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 077/831] #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 078/831] 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 079/831] 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 080/831] 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 081/831] 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 082/831] 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 083/831] #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 084/831] 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 085/831] 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 086/831] 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 087/831] 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 088/831] 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 089/831] 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 090/831] 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 091/831] 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 092/831] 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 093/831] 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 094/831] 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 095/831] 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 096/831] 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 097/831] 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 098/831] 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 099/831] 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